Sensor Fusion for Kinetis MCUs (ISSDK/KSDK version)
fusion.c
Go to the documentation of this file.
1 // Copyright (c) 2014, 2015, 2016, NXP Semiconductors N.V.,
2 // All rights reserved.
3 //
4 // Redistribution and use in source and binary forms, with or without
5 // modification, are permitted provided that the following conditions are met:
6 // * Redistributions of source code must retain the above copyright
7 // notice, this list of conditions and the following disclaimer.
8 // * Redistributions in binary form must reproduce the above copyright
9 // notice, this list of conditions and the following disclaimer in the
10 // documentation and/or other materials provided with the distribution.
11 // * Neither the name of NXP Semiconductors N.V. nor the
12 // names of its contributors may be used to endorse or promote products
13 // derived from this software without specific prior written permission.
14 //
15 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
16 // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17 // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18 // DISCLAIMED. IN NO EVENT SHALL NXP SEMICONDUCTORS N.V. BE LIABLE FOR ANY
19 // DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
20 // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
21 // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
22 // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
24 // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 // This is the file that contains the fusion routines. It is STRONGLY RECOMMENDED
26 // that the casual developer NOT TOUCH THIS FILE. The mathematics behind this file
27 // is extremely complex, and it will be very easy (almost inevitable) that you screw
28 // it up.
29 
30 /*! \file fusion.c
31  \brief Lower level sensor fusion interface
32 */
33 
34 #include "stdio.h"
35 #include "math.h"
36 #include "stdlib.h"
37 
38 #include "sensor_fusion.h"
39 #include "fusion.h"
40 #include "orientation.h"
41 #include "matrix.h"
42 #include "approximations.h"
43 #include "drivers.h"
44 #include "control.h"
45 
46 //////////////////////////////////////////////////////////////////////////////////////////////////
47 // intialization functions for the sensor fusion algorithms
48 //////////////////////////////////////////////////////////////////////////////////////////////////
49 
50 // function initializes the sensor fusion and magnetic calibration and sets loopcounter to zero
52 {
53  // reset the quaternion type to the default packet type
54  // sfg->pControlSubsystem->QuaternionPacketType = sfg->pControlSubsystem->DefaultQuaternionPacketType;
55 
56  // force a reset of all the algorithms next time they execute
57  // the initialization will result in the default and current quaternion being set to the most sophisticated
58  // algorithm supported by the build
59 #if F_1DOF_P_BASIC
60  sfg->SV_1DOF_P_BASIC.resetflag = true;
61 #endif
62 #if F_3DOF_B_BASIC
63  sfg->SV_3DOF_B_BASIC.resetflag = true;
64 #endif
65 #if F_3DOF_G_BASIC
66  sfg->SV_3DOF_G_BASIC.resetflag = true;
67 #endif
68 #if F_3DOF_Y_BASIC
69  sfg->SV_3DOF_Y_BASIC.resetflag = true;
70 #endif
71 #if F_6DOF_GB_BASIC
72  sfg->SV_6DOF_GB_BASIC.resetflag = true;
73 #endif
74 #if F_6DOF_GY_KALMAN
75  sfg->SV_6DOF_GY_KALMAN.resetflag = true;
76 #endif
77 #if F_9DOF_GBY_KALMAN
78  sfg->SV_9DOF_GBY_KALMAN.resetflag = true;
79 #endif
80 
81  // reset the loop counter to zero for first iteration
82  sfg->loopcounter = 0;
83  return;
84 }
85 
86 void fFuseSensors(struct SV_1DOF_P_BASIC *pthisSV_1DOF_P_BASIC,
87  struct SV_3DOF_G_BASIC *pthisSV_3DOF_G_BASIC,
88  struct SV_3DOF_B_BASIC *pthisSV_3DOF_B_BASIC,
89  struct SV_3DOF_Y_BASIC *pthisSV_3DOF_Y_BASIC,
90  struct SV_6DOF_GB_BASIC *pthisSV_6DOF_GB_BASIC,
91  struct SV_6DOF_GY_KALMAN *pthisSV_6DOF_GY_KALMAN,
92  struct SV_9DOF_GBY_KALMAN *pthisSV_9DOF_GBY_KALMAN,
93  struct AccelSensor *pthisAccel,
94  struct MagSensor *pthisMag,
95  struct GyroSensor *pthisGyro,
96  struct PressureSensor *pthisPressure,
97  struct MagCalibration *pthisMagCal)
98 {
99  // 1DOF Pressure: call the low pass filter algorithm
100 #if F_1DOF_P_BASIC
101  if (pthisSV_1DOF_P_BASIC)
102  {
103  ARM_systick_start_ticks(&(pthisSV_1DOF_P_BASIC->systick));
104  fRun_1DOF_P_BASIC(pthisSV_1DOF_P_BASIC, pthisPressure);
105  pthisSV_1DOF_P_BASIC->systick = ARM_systick_elapsed_ticks(pthisSV_1DOF_P_BASIC->systick);
106  }
107 #endif
108 
109  // 3DOF Accel Basic: call the tilt algorithm
110 #if F_3DOF_G_BASIC
111  if (pthisSV_3DOF_G_BASIC)
112  {
113  ARM_systick_start_ticks(&(pthisSV_3DOF_G_BASIC->systick));
114  fRun_3DOF_G_BASIC(pthisSV_3DOF_G_BASIC, pthisAccel);
115  pthisSV_3DOF_G_BASIC->systick = ARM_systick_elapsed_ticks(pthisSV_3DOF_G_BASIC->systick);
116  }
117 #endif
118 
119  // 3DOF Magnetometer Basic: call the 2D vehicle compass algorithm
120 #if F_3DOF_B_BASIC
121  if (pthisSV_3DOF_B_BASIC)
122  {
123  ARM_systick_start_ticks(&(pthisSV_3DOF_B_BASIC->systick));
124  fRun_3DOF_B_BASIC(pthisSV_3DOF_B_BASIC, pthisMag);
125  pthisSV_3DOF_B_BASIC->systick = ARM_systick_elapsed_ticks(pthisSV_3DOF_B_BASIC->systick);
126  }
127 #endif
128 
129  // 3DOF Gyro Basic: call the gyro integration algorithm
130 #if F_3DOF_Y_BASIC
131  if (pthisSV_3DOF_Y_BASIC)
132  {
133  ARM_systick_start_ticks(&(pthisSV_3DOF_Y_BASIC->systick));
134  fRun_3DOF_Y_BASIC(pthisSV_3DOF_Y_BASIC, pthisGyro);
135  pthisSV_3DOF_Y_BASIC->systick = ARM_systick_elapsed_ticks(pthisSV_3DOF_Y_BASIC->systick);
136  }
137 #endif
138 
139  // 6DOF Accel / Mag: Basic: call the eCompass orientation algorithm
140 #if F_6DOF_GB_BASIC
141  if (pthisSV_6DOF_GB_BASIC)
142  {
143  ARM_systick_start_ticks(&(pthisSV_6DOF_GB_BASIC->systick));
144  fRun_6DOF_GB_BASIC(pthisSV_6DOF_GB_BASIC, pthisMag, pthisAccel);
145  pthisSV_6DOF_GB_BASIC->systick = ARM_systick_elapsed_ticks(pthisSV_6DOF_GB_BASIC->systick);
146  }
147 #endif
148 
149  // 6DOF Accel / Gyro: call the Kalman filter orientation algorithm
150 #if F_6DOF_GY_KALMAN
151  if (pthisSV_6DOF_GY_KALMAN)
152  {
153  ARM_systick_start_ticks(&(pthisSV_6DOF_GY_KALMAN->systick));
154  fRun_6DOF_GY_KALMAN(pthisSV_6DOF_GY_KALMAN, pthisAccel, pthisGyro);
155  pthisSV_6DOF_GY_KALMAN->systick = ARM_systick_elapsed_ticks(pthisSV_6DOF_GY_KALMAN->systick);
156  }
157 #endif
158 
159  // 9DOF Accel / Mag / Gyro: call the Kalman filter orientation algorithm
160 #if F_9DOF_GBY_KALMAN
161  if (pthisSV_9DOF_GBY_KALMAN)
162  {
163  ARM_systick_start_ticks(&(pthisSV_9DOF_GBY_KALMAN->systick));
164  fRun_9DOF_GBY_KALMAN(pthisSV_9DOF_GBY_KALMAN, pthisAccel, pthisMag,
165  pthisGyro, pthisMagCal);
166  pthisSV_9DOF_GBY_KALMAN->systick = ARM_systick_elapsed_ticks(pthisSV_9DOF_GBY_KALMAN->systick);
167  }
168 #endif
169  return;
170 }
171 
172 void fInit_1DOF_P_BASIC(struct SV_1DOF_P_BASIC *pthisSV,
173  struct PressureSensor *pthisPressure, float flpftimesecs)
174 {
175  // set algorithm sampling interval (typically 25Hz) and low pass filter
176  // Note: the MPL3115 sensor only updates its output every 1s and is therefore repeatedly oversampled at 25Hz
177  // but executing the exponenial filter at the 25Hz rate also performs an interpolation giving smoother output.
178  // set algorithm sampling interval (typically 25Hz)
179  pthisSV->fdeltat = 1.0F / (float) FUSION_HZ;
180 
181  // set low pass filter constant with maximum value 1.0 (all pass) decreasing to 0.0 (increasing low pass)
182  if (flpftimesecs > pthisSV->fdeltat)
183  pthisSV->flpf = pthisSV->fdeltat / flpftimesecs;
184  else
185  pthisSV->flpf = 1.0F;
186 
187  // initialize the low pass filters to current measurement
188  pthisSV->fLPH = pthisPressure->fH;
189  pthisSV->fLPT = pthisPressure->fT;
190 
191  // clear the reset flag
192  pthisSV->resetflag = false;
193 
194  return;
195 } // end fInit_1DOF_P_BASIC
196 
197 void fInit_3DOF_G_BASIC(struct SV_3DOF_G_BASIC *pthisSV,
198  struct AccelSensor *pthisAccel, float flpftimesecs)
199 {
200  // set algorithm sampling interval (typically 25Hz)
201  pthisSV->fdeltat = 1.0F / (float) FUSION_HZ;
202 
203  // set low pass filter constant with maximum value 1.0 (all pass) decreasing to 0.0 (increasing low pass)
204  if (flpftimesecs > pthisSV->fdeltat)
205  pthisSV->flpf = pthisSV->fdeltat / flpftimesecs;
206  else
207  pthisSV->flpf = 1.0F;
208 
209  // apply the tilt estimation algorithm to initialize the low pass orientation matrix and quaternion
210 #if THISCOORDSYSTEM == NED
211  f3DOFTiltNED(pthisSV->fLPR, pthisAccel->fGc);
212 #elif THISCOORDSYSTEM == ANDROID
213  f3DOFTiltAndroid(pthisSV->fLPR, pthisAccel->fGc);
214 #else // WIN8
215  f3DOFTiltWin8(pthisSV->fLPR, pthisAccel->fGc);
216 #endif
217  fQuaternionFromRotationMatrix(pthisSV->fLPR, &(pthisSV->fLPq));
218 
219  // update the default quaternion type supported to the most sophisticated
220  //if (globals.DefaultQuaternionPacketType < Q3)
221  //globals.QuaternionPacketType = globals.DefaultQuaternionPacketType = Q3;
222  // clear the reset flag
223  pthisSV->resetflag = false;
224 
225  return;
226 } // end fInit_3DOF_G_BASIC
227 
228 void fInit_3DOF_B_BASIC(struct SV_3DOF_B_BASIC *pthisSV,
229  struct MagSensor *pthisMag, float flpftimesecs)
230 {
231  // set algorithm sampling interval (typically 25Hz)
232  pthisSV->fdeltat = 1.0F / (float) FUSION_HZ;
233 
234  // set low pass filter constant with maximum value 1.0 (all pass) decreasing to 0.0 (increasing low pass)
235  if (flpftimesecs > pthisSV->fdeltat)
236  pthisSV->flpf = pthisSV->fdeltat / flpftimesecs;
237  else
238  pthisSV->flpf = 1.0F;
239 
240  // initialize the low pass filtered magnetometer orientation matrix and quaternion using fBc
241 #if THISCOORDSYSTEM == NED
242  f3DOFMagnetometerMatrixNED(pthisSV->fLPR, pthisMag->fBc);
243 #elif THISCOORDSYSTEM == ANDROID
244  f3DOFMagnetometerMatrixAndroid(pthisSV->fLPR, pthisMag->fBc);
245 #else // WIN8
246  f3DOFMagnetometerMatrixWin8(pthisSV->fLPR, pthisMag->fBc);
247 #endif
248  fQuaternionFromRotationMatrix(pthisSV->fLPR, &(pthisSV->fLPq));
249 
250  // update the default quaternion type supported to the most sophisticated
251  //if (globals.DefaultQuaternionPacketType < Q3M)
252  //globals.QuaternionPacketType = globals.DefaultQuaternionPacketType = Q3M;
253  // clear the reset flag
254  pthisSV->resetflag = false;
255 
256  return;
257 } // end fInit_3DOF_B_BASIC
258 
259 void fInit_3DOF_Y_BASIC(struct SV_3DOF_Y_BASIC *pthisSV)
260 {
261  // compute the sampling time intervals (secs)
262  pthisSV->fdeltat = 1.0F / (float) FUSION_HZ;
263 
264  // initialize orientation estimate to flat
265  f3x3matrixAeqI(pthisSV->fR);
266  fqAeq1(&(pthisSV->fq));
267 
268  // update the default quaternion type supported to the most sophisticated
269  //if (globals.DefaultQuaternionPacketType < Q3G)
270  //globals.QuaternionPacketType = globals.DefaultQuaternionPacketType = Q3G;
271  // clear the reset flag
272  pthisSV->resetflag = false;
273 
274  return;
275 } // end fInit_3DOF_Y_BASIC
276 
278  struct AccelSensor *pthisAccel,
279  struct MagSensor *pthisMag, float flpftimesecs)
280 {
281  float ftmp;
282 
283  // set algorithm sampling interval (typically 25Hz)
284  pthisSV->fdeltat = 1.0F / (float) FUSION_HZ;
285 
286  // set low pass filter constant with maximum value 1.0 (all pass) decreasing to 0.0 (increasing low pass)
287  if (flpftimesecs > pthisSV->fdeltat)
288  pthisSV->flpf = pthisSV->fdeltat / flpftimesecs;
289  else
290  pthisSV->flpf = 1.0F;
291 
292  // initialize the instantaneous orientation matrix, inclination angle and quaternion
293 #if THISCOORDSYSTEM == NED
294  feCompassNED(pthisSV->fLPR, &(pthisSV->fLPDelta), &ftmp, &ftmp, pthisMag->fBc, pthisAccel->fGc, &ftmp, &ftmp );
295 #elif THISCOORDSYSTEM == ANDROID
296  feCompassAndroid(pthisSV->fLPR, &(pthisSV->fLPDelta), &ftmp, &ftmp, pthisMag->fBc, pthisAccel->fGc, &ftmp, &ftmp);
297 #else // WIN8
298  feCompassWin8(pthisSV->fLPR, &(pthisSV->fLPDelta), &ftmp, &ftmp, pthisMag->fBc, pthisAccel->fGc, &ftmp, &ftmp);
299 #endif
300  fQuaternionFromRotationMatrix(pthisSV->fLPR, &(pthisSV->fLPq));
301 
302  // update the default quaternion type supported to the most sophisticated
303  //if (globals.DefaultQuaternionPacketType < Q6MA)
304  // globals.QuaternionPacketType = globals.DefaultQuaternionPacketType = Q6MA;
305 
306  // clear the reset flag
307  pthisSV->resetflag = false;
308 
309  return;
310 } // end fInit_6DOF_GB_BASIC
311 
312 // function initalizes the 6DOF accel + gyro Kalman filter algorithm
314  struct AccelSensor *pthisAccel,
315  struct GyroSensor *pthisGyro)
316 {
317  float *pFlash; // pointer to flash float words
318  int8 i; // loop counter
319 
320  // compute and store useful product terms to save floating point calculations later
321  pthisSV->fdeltat = 1.0F / (float) FUSION_HZ;
322  pthisSV->fQwbOver3 = FQWB_6DOF_GY_KALMAN / 3.0F;
323  pthisSV->fAlphaOver2 = FPIOVER180 * pthisSV->fdeltat / 2.0F;
324  pthisSV->fAlphaSqOver4 = pthisSV->fAlphaOver2 * pthisSV->fAlphaOver2;
325  pthisSV->fAlphaQwbOver6 = pthisSV->fAlphaOver2 * pthisSV->fQwbOver3;
327  pthisSV->fMaxGyroOffsetChange = sqrtf(fabs(FQWB_9DOF_GBY_KALMAN)) / (float) FUSION_HZ;
328 
329  // zero the a posteriori gyro offset and error vectors
330  for (i = CHX; i <= CHZ; i++)
331  {
332  pthisSV->fqgErrPl[i] = 0.0F;
333  pthisSV->fbErrPl[i] = 0.0F;
334  }
335 
336  // check to see if a gyro calibration exists in flash
337  // the standard value for erased flash is 0xFF in each byte but for portability check against 0x12345678
338  pFlash = (float *) (CALIBRATION_NVM_ADDR + GYRO_NVM_OFFSET);
339  if (*((uint32 *) pFlash++) == 0x12345678)
340  {
341  // copy the gyro calibration from flash into the state vector
342  for (i = CHX; i <= CHZ; i++) pthisSV->fbPl[i] = *(pFlash++);
343  }
344  else
345  {
346  // set the gyro offset to the current measurement if within limits
347  for (i = CHX; i <= CHZ; i++)
348  {
349  if ((pthisGyro->fYs[i] >= FMIN_6DOF_GY_BPL) &&
350  (pthisGyro->fYs[i] <= FMAX_6DOF_GY_BPL))
351  pthisSV->fbPl[i] = pthisGyro->fYs[i];
352  else
353  pthisSV->fbPl[i] = 0.0F;
354  }
355  }
356 
357  // initialize the a posteriori orientation state vector to the tilt orientation
358 #if THISCOORDSYSTEM == NED
359  f3DOFTiltNED(pthisSV->fRPl, pthisAccel->fGc);
360 #elif THISCOORDSYSTEM == ANDROID
361  f3DOFTiltAndroid(pthisSV->fRPl, pthisAccel->fGc);
362 #else // Win8
363  f3DOFTiltWin8(pthisSV->fRPl, pthisAccel->fGc);
364 #endif
365  fQuaternionFromRotationMatrix(pthisSV->fRPl, &(pthisSV->fqPl));
366 
367  // update the default quaternion type supported to the most sophisticated
368  //if (globals.DefaultQuaternionPacketType < Q6AG)
369  //globals.QuaternionPacketType = globals.DefaultQuaternionPacketType = Q6AG;
370 
371  // clear the reset flag
372  pthisSV->resetflag = false;
373 
374  return;
375 } // end fInit_6DOF_GY_KALMAN
376 
377 // function initializes the 9DOF Kalman filter
378 void fInit_9DOF_GBY_KALMAN(struct SV_9DOF_GBY_KALMAN *pthisSV, struct AccelSensor *pthisAccel, struct MagSensor *pthisMag,
379  struct GyroSensor *pthisGyro, struct MagCalibration *pthisMagCal)
380 {
381  float ftmp;// scratch
382  float *pFlash;// pointer to flash float words
383  int8 i;// loop counter
384 
385  // compute and store useful product terms to save floating point calculations later
386  pthisSV->fdeltat = 1.0F / (float) FUSION_HZ;
387  pthisSV->fgdeltat = GTOMSEC2 * pthisSV->fdeltat;
388  pthisSV->fQwbOver3 = FQWB_9DOF_GBY_KALMAN / 3.0F;
389  pthisSV->fAlphaOver2 = FPIOVER180 * pthisSV->fdeltat / 2.0F;
390  pthisSV->fAlphaSqOver4 = pthisSV->fAlphaOver2 * pthisSV->fAlphaOver2;
391  pthisSV->fAlphaQwbOver6 = pthisSV->fAlphaOver2 * pthisSV->fQwbOver3;
393  pthisSV->fMaxGyroOffsetChange = sqrtf(fabs(FQWB_9DOF_GBY_KALMAN)) / (float)FUSION_HZ;
394 
395  // zero the a posteriori error vectors and inertial outputs
396  for (i = CHX; i <= CHZ; i++) {
397  pthisSV->fqgErrPl[i] = 0.0F;
398  pthisSV->fqmErrPl[i] = 0.0F;
399  pthisSV->fbErrPl[i] = 0.0F;
400  pthisSV->fVelGl[i] = 0.0F;
401  pthisSV->fDisGl[i] = 0.0F;
402  }
403 
404  // check to see if a gyro calibration exists in flash
405  // the standard value for erased flash is 0xFF in each byte but for portability check against 0x12345678
406  pFlash = (float *) (CALIBRATION_NVM_ADDR + GYRO_NVM_OFFSET);
407  if (*((uint32*) pFlash++) == 0x12345678) {
408  // copy the gyro calibration from flash into the state vector
409  for (i = CHX; i <= CHZ; i++)
410  pthisSV->fbPl[i] = *(pFlash++);
411  } else {
412  // set the gyro offset to the current measurement if within limits
413  for (i = CHX; i <= CHZ; i++) {
414  if ((pthisGyro->fYs[i] >= FMIN_9DOF_GBY_BPL) && (pthisGyro->fYs[i] <= FMAX_9DOF_GBY_BPL))
415  pthisSV->fbPl[i] = pthisGyro->fYs[i];
416  else
417  pthisSV->fbPl[i] = 0.0F;
418  }
419  }
420 
421  // initialize the posteriori orientation state vector to the instantaneous eCompass orientation
422  pthisSV->iFirstAccelMagLock = false;
423 #if THISCOORDSYSTEM == NED
424  feCompassNED(pthisSV->fRPl, &(pthisSV->fDeltaPl), &(pthisSV->fsinDeltaPl), &(pthisSV->fcosDeltaPl),
425  pthisMag->fBc, pthisAccel->fGc, &ftmp, &ftmp);
426 #elif THISCOORDSYSTEM == ANDROID
427  feCompassAndroid(pthisSV->fRPl, &(pthisSV->fDeltaPl), &(pthisSV->fsinDeltaPl), &(pthisSV->fcosDeltaPl),
428  pthisMag->fBc, pthisAccel->fGc, &ftmp, &ftmp);
429 #else // WIN8
430  feCompassWin8(pthisSV->fRPl, &(pthisSV->fDeltaPl), &(pthisSV->fsinDeltaPl), &(pthisSV->fcosDeltaPl),
431  pthisMag->fBc, pthisAccel->fGc, &ftmp, &ftmp);
432 #endif
433  fQuaternionFromRotationMatrix(pthisSV->fRPl, &(pthisSV->fqPl));
434 
435  // clear the reset flag
436  pthisSV->resetflag = false;
437 
438  return;
439 } // end fInit_9DOF_GBY_KALMAN
440 
441 //////////////////////////////////////////////////////////////////////////////////////////////////
442 
443 // run time functions for the sensor fusion algorithms
444 //////////////////////////////////////////////////////////////////////////////////////////////////
445 
446 // 1DOF pressure function
447 void fRun_1DOF_P_BASIC(struct SV_1DOF_P_BASIC *pthisSV,
448  struct PressureSensor *pthisPressure)
449 {
450  // if requested, do a reset and return
451  if (pthisSV->resetflag)
452  {
453  fInit_1DOF_P_BASIC(pthisSV, pthisPressure, FLPFSECS_1DOF_P_BASIC);
454  return;
455  }
456 
457  // exponentially low pass filter the pressure and temperature.
458  // when executed at (typically) 25Hz, this filter interpolates the raw signals which are
459  // output every 1s in Auto Acquisition mode.
460  pthisSV->fLPH += pthisSV->flpf * (pthisPressure->fH - pthisSV->fLPH);
461  pthisSV->fLPT += pthisSV->flpf * (pthisPressure->fT - pthisSV->fLPT);
462 
463  return;
464 } // end fRun_1DOF_P_BASIC
465 
466 // 3DOF orientation function which calls tilt functions and implements low pass filters
467 void fRun_3DOF_G_BASIC(struct SV_3DOF_G_BASIC *pthisSV,
468  struct AccelSensor *pthisAccel)
469 {
470  // if requested, do a reset and return
471  if (pthisSV->resetflag)
472  {
473  fInit_3DOF_G_BASIC(pthisSV, pthisAccel, FLPFSECS_3DOF_G_BASIC);
474  return;
475  }
476 
477  // apply the tilt estimation algorithm to get the instantaneous orientation matrix
478 #if THISCOORDSYSTEM == NED
479  f3DOFTiltNED(pthisSV->fR, pthisAccel->fGc);
480 #elif THISCOORDSYSTEM == ANDROID
481  f3DOFTiltAndroid(pthisSV->fR, pthisAccel->fGc);
482 #else // WIN8
483  f3DOFTiltWin8(pthisSV->fR, pthisAccel->fGc);
484 #endif
485 
486  // compute the instantaneous quaternion and low pass filter
487  fQuaternionFromRotationMatrix(pthisSV->fR, &(pthisSV->fq));
488  fLPFOrientationQuaternion(&(pthisSV->fq), &(pthisSV->fLPq), pthisSV->flpf,
489  pthisSV->fdeltat, pthisSV->fOmega);
490 
491  // compute the low pass rotation matrix and rotation vector from low pass filtered quaternion
492  fRotationMatrixFromQuaternion(pthisSV->fLPR, &(pthisSV->fLPq));
493  fRotationVectorDegFromQuaternion(&(pthisSV->fLPq), pthisSV->fLPRVec);
494 
495  // calculate the Euler angles from the low pass orientation matrix
496 #if THISCOORDSYSTEM == NED
497  fNEDAnglesDegFromRotationMatrix(pthisSV->fLPR, &(pthisSV->fLPPhi),
498  &(pthisSV->fLPThe), &(pthisSV->fLPPsi),
499  &(pthisSV->fLPRho), &(pthisSV->fLPChi));
500 #elif THISCOORDSYSTEM == ANDROID
501  fAndroidAnglesDegFromRotationMatrix(pthisSV->fLPR, &(pthisSV->fLPPhi),
502  &(pthisSV->fLPThe), &(pthisSV->fLPPsi),
503  &(pthisSV->fLPRho), &(pthisSV->fLPChi));
504 #else // WIN8
505  fWin8AnglesDegFromRotationMatrix(pthisSV->fLPR, &(pthisSV->fLPPhi),
506  &(pthisSV->fLPThe), &(pthisSV->fLPPsi),
507  &(pthisSV->fLPRho), &(pthisSV->fLPChi));
508 #endif
509 
510  // force the yaw and compass angles to zero
511  pthisSV->fLPPsi = pthisSV->fLPRho = 0.0F;
512 
513  return;
514 } // end fRun_3DOF_G_BASIC
515 
516 // 2D automobile eCompass
517 void fRun_3DOF_B_BASIC(struct SV_3DOF_B_BASIC *pthisSV, struct MagSensor *pthisMag)
518 {
519  // if requested, do a reset and return
520  if (pthisSV->resetflag)
521  {
522  fInit_3DOF_B_BASIC(pthisSV, pthisMag, FLPFSECS_3DOF_B_BASIC);
523  return;
524  }
525 
526  // calculate the 3DOF magnetometer orientation matrix from fBc
527 #if THISCOORDSYSTEM == NED
528  f3DOFMagnetometerMatrixNED(pthisSV->fR, pthisMag->fBc);
529 #elif THISCOORDSYSTEM == ANDROID
530  f3DOFMagnetometerMatrixAndroid(pthisSV->fR, pthisMag->fBc);
531 #else // WIN8
532  f3DOFMagnetometerMatrixWin8(pthisSV->fR, pthisMag->fBc);
533 #endif
534 
535  // compute the instantaneous quaternion and low pass filter
536  fQuaternionFromRotationMatrix(pthisSV->fR, &(pthisSV->fq));
537  fLPFOrientationQuaternion(&(pthisSV->fq), &(pthisSV->fLPq), pthisSV->flpf,
538  pthisSV->fdeltat, pthisSV->fOmega);
539 
540  // compute the low pass rotation matrix and rotation vector from low pass filtered quaternion
541  fRotationMatrixFromQuaternion(pthisSV->fLPR, &(pthisSV->fLPq));
542  fRotationVectorDegFromQuaternion(&(pthisSV->fLPq), pthisSV->fLPRVec);
543 
544  // calculate the Euler angles from the low pass orientation matrix
545 #if THISCOORDSYSTEM == NED
546  fNEDAnglesDegFromRotationMatrix(pthisSV->fLPR, &(pthisSV->fLPPhi),
547  &(pthisSV->fLPThe), &(pthisSV->fLPPsi),
548  &(pthisSV->fLPRho), &(pthisSV->fLPChi));
549 #elif THISCOORDSYSTEM == ANDROID
550  fAndroidAnglesDegFromRotationMatrix(pthisSV->fLPR, &(pthisSV->fLPPhi),
551  &(pthisSV->fLPThe), &(pthisSV->fLPPsi),
552  &(pthisSV->fLPRho), &(pthisSV->fLPChi));
553 #else // WIN8
554  fWin8AnglesDegFromRotationMatrix(pthisSV->fLPR, &(pthisSV->fLPPhi),
555  &(pthisSV->fLPThe), &(pthisSV->fLPPsi),
556  &(pthisSV->fLPRho), &(pthisSV->fLPChi));
557 #endif
558  return;
559 }
560 
561 // basic gyro integration function
562 void fRun_3DOF_Y_BASIC(struct SV_3DOF_Y_BASIC *pthisSV,
563  struct GyroSensor *pthisGyro)
564 {
565  Quaternion ftmpq; // scratch quaternion
566  int8 i; // loop counter
567 
568  // if requested, do a reset and return
569  if (pthisSV->resetflag)
570  {
571  fInit_3DOF_Y_BASIC(pthisSV);
572  return;
573  }
574 
575  // perform an approximate gyro integration for this algorithm using the average of all gyro measurments
576  // in the FIFO rather than computing the products of the individual quaternions.
577  // the reason is this algorithm does not estimate and subtract the gyro offset so any loss of integration accuracy
578  // from using the average gyro measurement is irrelevant.
579  // calculate the angular velocity (deg/s) and rotation vector from the average measurement
580  for (i = CHX; i <= CHZ; i++) pthisSV->fOmega[i] = pthisGyro->fYs[i];
581 
582  // compute the incremental rotation quaternion ftmpq, rotate the orientation quaternion fq
583  // and re-normalize fq to prevent error propagation
584  fQuaternionFromRotationVectorDeg(&ftmpq, pthisSV->fOmega, pthisSV->fdeltat);
585  qAeqAxB(&(pthisSV->fq), &ftmpq);
586  fqAeqNormqA(&(pthisSV->fq));
587 
588  // get the rotation matrix and rotation vector from the orientation quaternion fq
589  fRotationMatrixFromQuaternion(pthisSV->fR, &(pthisSV->fq));
590  fRotationVectorDegFromQuaternion(&(pthisSV->fq), pthisSV->fRVec);
591 
592  // compute the Euler angles from the orientation matrix
593 #if THISCOORDSYSTEM == NED
594  fNEDAnglesDegFromRotationMatrix(pthisSV->fR, &(pthisSV->fPhi),
595  &(pthisSV->fThe), &(pthisSV->fPsi),
596  &(pthisSV->fRho), &(pthisSV->fChi));
597 #elif THISCOORDSYSTEM == ANDROID
598  fAndroidAnglesDegFromRotationMatrix(pthisSV->fR, &(pthisSV->fPhi),
599  &(pthisSV->fThe), &(pthisSV->fPsi),
600  &(pthisSV->fRho), &(pthisSV->fChi));
601 #else // WIN8
602  fWin8AnglesDegFromRotationMatrix(pthisSV->fR, &(pthisSV->fPhi),
603  &(pthisSV->fThe), &(pthisSV->fPsi),
604  &(pthisSV->fRho), &(pthisSV->fChi));
605 #endif
606  return;
607 } // end fRun_3DOF_Y_BASIC
608 
609 // 6DOF eCompass orientation function
611  struct MagSensor *pthisMag, struct AccelSensor *pthisAccel)
612 {
613  float ftmp1, ftmp2, ftmp3, ftmp4;
614 
615  // if requested, do a reset and return
616  if (pthisSV->resetflag)
617  {
618  fInit_6DOF_GB_BASIC(pthisSV, pthisAccel, pthisMag,
620  return;
621  }
622 
623  // call the eCompass algorithm to get the instantaneous orientation matrix and inclination angle
624 #if THISCOORDSYSTEM == NED
625  feCompassNED(pthisSV->fR, &(pthisSV->fDelta), &ftmp1, &ftmp2, pthisMag->fBc, pthisAccel->fGc, &ftmp3, &ftmp4);
626 #elif THISCOORDSYSTEM == ANDROID
627  feCompassAndroid(pthisSV->fR, &(pthisSV->fDelta), &ftmp1, &ftmp2, pthisMag->fBc, pthisAccel->fGc, &ftmp3, &ftmp4);
628 #else // WIN8
629  feCompassWin8(pthisSV->fR, &(pthisSV->fDelta), &ftmp1, &ftmp2, pthisMag->fBc, pthisAccel->fGc, &ftmp3, &ftmp4);
630 #endif
631 
632  // compute the instantaneous quaternion and low pass filter
633  fQuaternionFromRotationMatrix(pthisSV->fR, &(pthisSV->fq));
634  fLPFOrientationQuaternion(&(pthisSV->fq), &(pthisSV->fLPq), pthisSV->flpf,
635  pthisSV->fdeltat, pthisSV->fOmega);
636 
637  // compute the low pass rotation matrix and rotation vector from low pass filtered quaternion
638  fRotationMatrixFromQuaternion(pthisSV->fLPR, &(pthisSV->fLPq));
639  fRotationVectorDegFromQuaternion(&(pthisSV->fLPq), pthisSV->fLPRVec);
640 
641  // calculate the Euler angles from the low pass orientation matrix
642 #if THISCOORDSYSTEM == NED
643  fNEDAnglesDegFromRotationMatrix(pthisSV->fLPR, &(pthisSV->fLPPhi),
644  &(pthisSV->fLPThe), &(pthisSV->fLPPsi),
645  &(pthisSV->fLPRho), &(pthisSV->fLPChi));
646 #elif THISCOORDSYSTEM == ANDROID
647  fAndroidAnglesDegFromRotationMatrix(pthisSV->fLPR, &(pthisSV->fLPPhi),
648  &(pthisSV->fLPThe), &(pthisSV->fLPPsi),
649  &(pthisSV->fLPRho), &(pthisSV->fLPChi));
650 #else // WIN8
651  fWin8AnglesDegFromRotationMatrix(pthisSV->fLPR, &(pthisSV->fLPPhi),
652  &(pthisSV->fLPThe), &(pthisSV->fLPPsi),
653  &(pthisSV->fLPRho), &(pthisSV->fLPChi));
654 #endif
655 
656  // low pass filter the geomagnetic inclination angle with a simple exponential filter
657  pthisSV->fLPDelta += pthisSV->flpf * (pthisSV->fDelta - pthisSV->fLPDelta);
658 
659  return;
660 } // end fRun_6DOF_GB_BASIC
661 
662 // 6DOF accelerometer+gyroscope orientation function implemented using indirect complementary Kalman filter
664  struct AccelSensor *pthisAccel,
665  struct GyroSensor *pthisGyro)
666 {
667  // local scalars and arrays
668  float ftmpMi3x1[3]; // temporary vector used for a priori calculations
669  float ftmp3DOF3x1[3]; // temporary vector used for 3DOF calculations
670  float ftmpA3x3[3][3]; // scratch 3x3 matrix
671  float fQvGQa; // accelerometer noise covariance to 1g sphere
672  float fC3x6ik; // element i, k of measurement matrix C
673  float fC3x6jk; // element j, k of measurement matrix C
674  float fmodGc; // modulus of fGc[]
675  Quaternion fqMi; // a priori orientation quaternion
676  Quaternion ftmpq; // scratch quaternion
677  float ftmp; // scratch float
678  int8 ierror; // matrix inversion error flag
679  int8 i,
680  j,
681  k; // loop counters
682 
683  // working arrays for 3x3 matrix inversion
684  float *pfRows[3];
685  int8 iColInd[3];
686  int8 iRowInd[3];
687  int8 iPivot[3];
688 
689  // if requested, do a reset initialization with no further processing
690  if (pthisSV->resetflag)
691  {
692  fInit_6DOF_GY_KALMAN(pthisSV, pthisAccel, pthisGyro);
693  return;
694  }
695 
696  // compute the average angular velocity (used for display only) from the average measurement minus gyro offset
697  for (i = CHX; i <= CHZ; i++)
698  pthisSV->fOmega[i] = (float) pthisGyro->iYs[i] *
699  pthisGyro->fDegPerSecPerCount -
700  pthisSV->fbPl[i];
701 
702  // initialize the a priori orientation quaternion fqMi to the previous iteration's a posteriori estimate
703  // and incrementally rotate fqMi by the contents of the gyro FIFO buffer
704  fqMi = pthisSV->fqPl;
705  if (pthisGyro->iFIFOCount > 0)
706  {
707  // set ftmp to the interval between the FIFO gyro measurements
708  ftmp = pthisSV->fdeltat / (float) pthisGyro->iFIFOCount;
709 
710  // normal case, loop over all the buffered gyroscope measurements
711  for (j = 0; j < pthisGyro->iFIFOCount; j++)
712  {
713  // calculate the instantaneous angular velocity subtracting the gyro offset
714  for (i = CHX; i <= CHZ; i++)
715  ftmpMi3x1[i] = (float) pthisGyro->iYsFIFO[j][i] *
716  pthisGyro->fDegPerSecPerCount -
717  pthisSV->fbPl[i];
718 
719  // compute the incremental rotation quaternion ftmpq and integrate the a priori orientation quaternion fqMi
720  fQuaternionFromRotationVectorDeg(&ftmpq, ftmpMi3x1, ftmp);
721  qAeqAxB(&fqMi, &ftmpq);
722  }
723  }
724  else
725  {
726  // special case with no new FIFO measurements, use the previous iteration's average gyro reading to compute
727  // the incremental rotation quaternion ftmpq and integrate the a priori orientation quaternion fqMi
728  fQuaternionFromRotationVectorDeg(&ftmpq, pthisSV->fOmega,
729  pthisSV->fdeltat);
730  qAeqAxB(&fqMi, &ftmpq);
731  }
732 
733  // set ftmp3DOF3x1 to the 3DOF gravity vector in the sensor frame
734  fmodGc = sqrtf(fabs(pthisAccel->fGc[CHX] * pthisAccel->fGc[CHX] +
735  pthisAccel->fGc[CHY] * pthisAccel->fGc[CHY] +
736  pthisAccel->fGc[CHZ] * pthisAccel->fGc[CHZ]));
737  if (fmodGc != 0.0F)
738  {
739  // normal non-freefall case
740  ftmp = 1.0F / fmodGc;
741  ftmp3DOF3x1[CHX] = pthisAccel->fGc[CHX] * ftmp;
742  ftmp3DOF3x1[CHY] = pthisAccel->fGc[CHY] * ftmp;
743  ftmp3DOF3x1[CHZ] = pthisAccel->fGc[CHZ] * ftmp;
744  }
745  else
746  {
747  // use zero tilt in case of freefall
748  ftmp3DOF3x1[CHX] = 0.0F;
749  ftmp3DOF3x1[CHY] = 0.0F;
750  ftmp3DOF3x1[CHZ] = 1.0F;
751  }
752 
753  // correct accelerometer gravity vector for different coordinate systems
754 #if THISCOORDSYSTEM == NED
755  // +1g in accelerometer z axis (z down) when PCB is flat so no correction needed
756 #elif THISCOORDSYSTEM == ANDROID
757  // +1g in accelerometer z axis (z up) when PCB is flat so negate the vector to obtain gravity
758  ftmp3DOF3x1[CHX] = -ftmp3DOF3x1[CHX];
759  ftmp3DOF3x1[CHY] = -ftmp3DOF3x1[CHY];
760  ftmp3DOF3x1[CHZ] = -ftmp3DOF3x1[CHZ];
761 #else // WIN8
762  // -1g in accelerometer z axis (z up) when PCB is flat so no correction needed
763 #endif
764 
765  // set ftmpMi3x1 to the a priori gravity vector in the sensor frame from the a priori quaternion
766  ftmpMi3x1[CHX] = 2.0F * (fqMi.q1 * fqMi.q3 - fqMi.q0 * fqMi.q2);
767  ftmpMi3x1[CHY] = 2.0F * (fqMi.q2 * fqMi.q3 + fqMi.q0 * fqMi.q1);
768  ftmpMi3x1[CHZ] = 2.0F * (fqMi.q0 * fqMi.q0 + fqMi.q3 * fqMi.q3) - 1.0F;
769 
770  // correct a priori gravity vector for different coordinate systems
771 #if THISCOORDSYSTEM == NED
772  // z axis is down (NED) so no correction needed
773 #else // ANDROID and WIN8
774  // z axis is up (ANDROID and WIN8 ENU) so no negate the vector to obtain gravity
775  ftmpMi3x1[CHX] = -ftmpMi3x1[CHX];
776  ftmpMi3x1[CHY] = -ftmpMi3x1[CHY];
777  ftmpMi3x1[CHZ] = -ftmpMi3x1[CHZ];
778 #endif
779 
780  // calculate the rotation quaternion between 3DOF and a priori gravity vectors (ignored minus signs cancel here)
781  // and copy the quaternion vector components to the measurement error vector fZErr
782  fveqconjgquq(&ftmpq, ftmp3DOF3x1, ftmpMi3x1);
783  pthisSV->fZErr[CHX] = ftmpq.q1;
784  pthisSV->fZErr[CHY] = ftmpq.q2;
785  pthisSV->fZErr[CHZ] = ftmpq.q3;
786 
787  // update Qw using the a posteriori error vectors from the previous iteration.
788  // as Qv increases or Qw decreases, K -> 0 and the Kalman filter is weighted towards the a priori prediction
789  // as Qv decreases or Qw increases, KC -> I and the Kalman filter is weighted towards the measurement.
790  // initialize Qw to all zeroes
791  for (i = 0; i < 6; i++)
792  for (j = 0; j < 6; j++) pthisSV->fQw6x6[i][j] = 0.0F;
793 
794  // partial diagonal gyro offset terms
795  pthisSV->fQw6x6[3][3] = pthisSV->fbErrPl[CHX] * pthisSV->fbErrPl[CHX];
796  pthisSV->fQw6x6[4][4] = pthisSV->fbErrPl[CHY] * pthisSV->fbErrPl[CHY];
797  pthisSV->fQw6x6[5][5] = pthisSV->fbErrPl[CHZ] * pthisSV->fbErrPl[CHZ];
798 
799  // diagonal gravity vector components
800  pthisSV->fQw6x6[0][0] = pthisSV->fqgErrPl[CHX] *
801  pthisSV->fqgErrPl[CHX] +
802  pthisSV->fAlphaSqQvYQwbOver12 +
803  pthisSV->fAlphaSqOver4 *
804  pthisSV->fQw6x6[3][3];
805  pthisSV->fQw6x6[1][1] = pthisSV->fqgErrPl[CHY] *
806  pthisSV->fqgErrPl[CHY] +
807  pthisSV->fAlphaSqQvYQwbOver12 +
808  pthisSV->fAlphaSqOver4 *
809  pthisSV->fQw6x6[4][4];
810  pthisSV->fQw6x6[2][2] = pthisSV->fqgErrPl[CHZ] *
811  pthisSV->fqgErrPl[CHZ] +
812  pthisSV->fAlphaSqQvYQwbOver12 +
813  pthisSV->fAlphaSqOver4 *
814  pthisSV->fQw6x6[5][5];
815 
816  // remaining diagonal gyro offset components
817  pthisSV->fQw6x6[3][3] += pthisSV->fQwbOver3;
818  pthisSV->fQw6x6[4][4] += pthisSV->fQwbOver3;
819  pthisSV->fQw6x6[5][5] += pthisSV->fQwbOver3;
820 
821  // off diagonal gravity and gyro offset components
822  pthisSV->fQw6x6[0][3] = pthisSV->fQw6x6[3][0] = pthisSV->fqgErrPl[CHX] *
823  pthisSV->fbErrPl[CHX] -
824  pthisSV->fAlphaOver2 *
825  pthisSV->fQw6x6[3][3];
826  pthisSV->fQw6x6[1][4] = pthisSV->fQw6x6[4][1] = pthisSV->fqgErrPl[CHY] *
827  pthisSV->fbErrPl[CHY] -
828  pthisSV->fAlphaOver2 *
829  pthisSV->fQw6x6[4][4];
830  pthisSV->fQw6x6[2][5] = pthisSV->fQw6x6[5][2] = pthisSV->fqgErrPl[CHZ] *
831  pthisSV->fbErrPl[CHZ] -
832  pthisSV->fAlphaOver2 *
833  pthisSV->fQw6x6[5][5];
834 
835  // calculate the vector fQv containing the diagonal elements of the measurement covariance matrix Qv
836  ftmp = fmodGc - 1.0F;
837  fQvGQa = 3.0F * ftmp * ftmp;
838  if (fQvGQa < FQVG_6DOF_GY_KALMAN) fQvGQa = FQVG_6DOF_GY_KALMAN;
839  pthisSV->fQv = ONEOVER12 * ftmp * ftmp + pthisSV->fAlphaSqQvYQwbOver12;
840 
841  // calculate the 6x3 Kalman gain matrix K = Qw * C^T * inv(C * Qw * C^T + Qv)
842  // set fQwCT6x3 = Qw.C^T where Qw has size 6x6 and C^T has size 6x3
843  for (i = 0; i < 6; i++) // loop over rows
844  {
845  for (j = 0; j < 3; j++) // loop over columns
846  {
847  pthisSV->fQwCT6x3[i][j] = 0.0F;
848 
849  // accumulate matrix sum
850  for (k = 0; k < 6; k++)
851  {
852  // determine fC3x6[j][k] since the matrix is highly sparse
853  fC3x6jk = 0.0F;
854  if (k == j) fC3x6jk = 1.0F;
855  if (k == (j + 3)) fC3x6jk = -pthisSV->fAlphaOver2;
856 
857  // accumulate fQwCT6x3[i][j] += Qw6x6[i][k] * C[j][k]
858  if ((pthisSV->fQw6x6[i][k] != 0.0F) && (fC3x6jk != 0.0F))
859  {
860  if (fC3x6jk == 1.0F)
861  pthisSV->fQwCT6x3[i][j] += pthisSV->fQw6x6[i][k];
862  else
863  pthisSV->fQwCT6x3[i][j] += pthisSV->fQw6x6[i][k] * fC3x6jk;
864  }
865  }
866  }
867  }
868 
869  // set symmetric ftmpA3x3 = C.(Qw.C^T) + Qv = C.fQwCT6x3 + Qv
870  for (i = 0; i < 3; i++) // loop over rows
871  {
872  for (j = i; j < 3; j++) // loop over on and above diagonal columns
873  {
874  // zero off diagonal and set diagonal to Qv
875  if (i == j)
876  ftmpA3x3[i][j] = pthisSV->fQv;
877  else
878  ftmpA3x3[i][j] = 0.0F;
879 
880  // accumulate matrix sum
881  for (k = 0; k < 6; k++)
882  {
883  // determine fC3x6[i][k]
884  fC3x6ik = 0.0F;
885  if (k == i) fC3x6ik = 1.0F;
886  if (k == (i + 3)) fC3x6ik = -pthisSV->fAlphaOver2;
887 
888  // accumulate ftmpA3x3[i][j] += C[i][k] & fQwCT6x3[k][j]
889  if ((fC3x6ik != 0.0F) && (pthisSV->fQwCT6x3[k][j] != 0.0F))
890  {
891  if (fC3x6ik == 1.0F)
892  ftmpA3x3[i][j] += pthisSV->fQwCT6x3[k][j];
893  else
894  ftmpA3x3[i][j] += fC3x6ik * pthisSV->fQwCT6x3[k][j];
895  }
896  }
897  }
898  }
899 
900  // set ftmpA3x3 below diagonal elements to above diagonal elements
901  ftmpA3x3[1][0] = ftmpA3x3[0][1];
902  ftmpA3x3[2][0] = ftmpA3x3[0][2];
903  ftmpA3x3[2][1] = ftmpA3x3[1][2];
904 
905  // invert ftmpA3x3 in situ to give ftmpA3x3 = inv(C * Qw * C^T + Qv) = inv(ftmpA3x3)
906  for (i = 0; i < 3; i++) pfRows[i] = ftmpA3x3[i];
907  fmatrixAeqInvA(pfRows, iColInd, iRowInd, iPivot, 3, &ierror);
908 
909  // on successful inversion set Kalman gain matrix fK6x3 = Qw * C^T * inv(C * Qw * C^T + Qv) = fQwCT6x3 * ftmpA3x3
910  if (!ierror)
911  {
912  // normal case
913  for (i = 0; i < 6; i++) // loop over rows
914  {
915  for (j = 0; j < 3; j++) // loop over columns
916  {
917  pthisSV->fK6x3[i][j] = 0.0F;
918  for (k = 0; k < 3; k++)
919  {
920  if ((pthisSV->fQwCT6x3[i][k] != 0.0F) &&
921  (ftmpA3x3[k][j] != 0.0F))
922  {
923  pthisSV->fK6x3[i][j] += pthisSV->fQwCT6x3[i][k] * ftmpA3x3[k][j];
924  }
925  }
926  }
927  }
928  }
929  else
930  {
931  // ftmpA3x3 was singular so set Kalman gain matrix fK6x3 to zero
932  for (i = 0; i < 6; i++) // loop over rows
933  {
934  for (j = 0; j < 3; j++) // loop over columns
935  {
936  pthisSV->fK6x3[i][j] = 0.0F;
937  }
938  }
939  }
940 
941  // calculate the a posteriori gravity and geomagnetic tilt quaternion errors and gyro offset error vector
942  // from the Kalman matrix fK6x3 and from the measurement error vector fZErr.
943  for (i = CHX; i <= CHZ; i++)
944  {
945  // gravity tilt vector error component
946  if (pthisSV->fK6x3[i][CHX] != 0.0F)
947  pthisSV->fqgErrPl[i] = pthisSV->fK6x3[i][CHX] * pthisSV->fZErr[CHX];
948  else
949  pthisSV->fqgErrPl[i] = 0.0F;
950  if (pthisSV->fK6x3[i][CHY] != 0.0F)
951  pthisSV->fqgErrPl[i] += pthisSV->fK6x3[i][CHY] * pthisSV->fZErr[CHY];
952  if (pthisSV->fK6x3[i][CHZ] != 0.0F)
953  pthisSV->fqgErrPl[i] += pthisSV->fK6x3[i][CHZ] * pthisSV->fZErr[CHZ];
954 
955  // gyro offset vector error component
956  if (pthisSV->fK6x3[i + 3][CHX] != 0.0F)
957  pthisSV->fbErrPl[i] = pthisSV->fK6x3[i + 3][CHX] * pthisSV->fZErr[CHX];
958  else
959  pthisSV->fbErrPl[i] = 0.0F;
960  if (pthisSV->fK6x3[i + 3][CHY] != 0.0F)
961  pthisSV->fbErrPl[i] += pthisSV->fK6x3[i + 3][CHY] * pthisSV->fZErr[CHY];
962  if (pthisSV->fK6x3[i + 3][CHZ] != 0.0F)
963  pthisSV->fbErrPl[i] += pthisSV->fK6x3[i + 3][CHZ] * pthisSV->fZErr[CHZ];
964  }
965 
966  // set ftmpq to the gravity tilt correction (conjugate) quaternion
967  ftmpq.q1 = -pthisSV->fqgErrPl[CHX];
968  ftmpq.q2 = -pthisSV->fqgErrPl[CHY];
969  ftmpq.q3 = -pthisSV->fqgErrPl[CHZ];
970  ftmp = ftmpq.q1 * ftmpq.q1 + ftmpq.q2 * ftmpq.q2 + ftmpq.q3 * ftmpq.q3;
971 
972  // determine the scalar component q0 to enforce normalization
973  if (ftmp <= 1.0F)
974  {
975  // normal case
976  ftmpq.q0 = sqrtf(fabsf(1.0F - ftmp));
977  }
978  else
979  {
980  // if vector component exceeds unity then set to 180 degree rotation and force normalization
981  ftmp = 1.0F / sqrtf(ftmp);
982  ftmpq.q0 = 0.0F;
983  ftmpq.q1 *= ftmp;
984  ftmpq.q2 *= ftmp;
985  ftmpq.q3 *= ftmp;
986  }
987 
988  // apply the gravity tilt correction quaternion so fqPl = fqMi.(fqgErrPl)* = fqMi.ftmpq and normalize
989  qAeqBxC(&(pthisSV->fqPl), &fqMi, &ftmpq);
990 
991  // normalize the a posteriori quaternion and compute the a posteriori rotation matrix and rotation vector
992  fqAeqNormqA(&(pthisSV->fqPl));
993  fRotationMatrixFromQuaternion(pthisSV->fRPl, &(pthisSV->fqPl));
994  fRotationVectorDegFromQuaternion(&(pthisSV->fqPl), pthisSV->fRVecPl);
995 
996  // update the a posteriori gyro offset vector: b+[k] = b-[k] - be+[k] = b+[k] - be+[k] (deg/s)
997  // limiting the correction to the maximum permitted by the random walk model
998  for (i = CHX; i <= CHZ; i++)
999  {
1000  if (pthisSV->fbErrPl[i] > pthisSV->fMaxGyroOffsetChange)
1001  pthisSV->fbPl[i] -= pthisSV->fMaxGyroOffsetChange;
1002  else if (pthisSV->fbErrPl[i] < -pthisSV->fMaxGyroOffsetChange)
1003  pthisSV->fbPl[i] += pthisSV->fMaxGyroOffsetChange;
1004  else
1005  pthisSV->fbPl[i] -= pthisSV->fbErrPl[i];
1006  }
1007 
1008  // compute the linear acceleration fAccGl in the global frame
1009  // first de-rotate the accelerometer measurement fGc from the sensor to global frame
1010  // using the transpose (inverse) of the orientation matrix fRPl
1011  fveqRu(pthisSV->fAccGl, pthisSV->fRPl, pthisAccel->fGc, 1);
1012 
1013  // sutract the fixed gravity vector in the global frame leaving linear acceleration
1014 #if THISCOORDSYSTEM == NED
1015  // gravity positive NED
1016  pthisSV->fAccGl[CHX] = -pthisSV->fAccGl[CHX];
1017  pthisSV->fAccGl[CHY] = -pthisSV->fAccGl[CHY];
1018  pthisSV->fAccGl[CHZ] = -(pthisSV->fAccGl[CHZ] - 1.0F);
1019 #elif THISCOORDSYSTEM == ANDROID
1020  // acceleration positive ENU
1021  pthisSV->fAccGl[CHZ] = pthisSV->fAccGl[CHZ] - 1.0F;
1022 #else // WIN8
1023  // gravity positive ENU
1024  pthisSV->fAccGl[CHX] = -pthisSV->fAccGl[CHX];
1025  pthisSV->fAccGl[CHY] = -pthisSV->fAccGl[CHY];
1026  pthisSV->fAccGl[CHZ] = -(pthisSV->fAccGl[CHZ] + 1.0F);
1027 #endif
1028 
1029  // compute the a posteriori Euler angles from the a posteriori orientation matrix fRPl
1030 #if THISCOORDSYSTEM == NED
1031  fNEDAnglesDegFromRotationMatrix(pthisSV->fRPl, &(pthisSV->fPhiPl),
1032  &(pthisSV->fThePl), &(pthisSV->fPsiPl),
1033  &(pthisSV->fRhoPl), &(pthisSV->fChiPl));
1034 #elif THISCOORDSYSTEM == ANDROID
1035  fAndroidAnglesDegFromRotationMatrix(pthisSV->fRPl, &(pthisSV->fPhiPl),
1036  &(pthisSV->fThePl), &(pthisSV->fPsiPl),
1037  &(pthisSV->fRhoPl), &(pthisSV->fChiPl));
1038 #else // WIN8
1039  fWin8AnglesDegFromRotationMatrix(pthisSV->fRPl, &(pthisSV->fPhiPl),
1040  &(pthisSV->fThePl), &(pthisSV->fPsiPl),
1041  &(pthisSV->fRhoPl), &(pthisSV->fChiPl));
1042 #endif
1043  return;
1044 } // end fRun_6DOF_GY_KALMAN
1045 #if F_9DOF_GBY_KALMAN
1046 // 9DOF accelerometer+magnetometer+gyroscope orientation function implemented using indirect complementary Kalman filter
1047 void fRun_9DOF_GBY_KALMAN(struct SV_9DOF_GBY_KALMAN *pthisSV,
1048  struct AccelSensor *pthisAccel,
1049  struct MagSensor *pthisMag,
1050  struct GyroSensor *pthisGyro,
1051  struct MagCalibration *pthisMagCal)
1052 {
1053  // local scalars and arrays
1054  float ftmpA6x6[6][6]; // scratch 6x6 matrix
1055  float fRMi[3][3]; // a priori orientation matrix
1056  float fR6DOF[3][3]; // eCompass (6DOF accelerometer+magnetometer) orientation matrix
1057  float fgMi[3]; // a priori estimate of the gravity vector (sensor frame)
1058  float fmMi[3]; // a priori estimate of the geomagnetic vector (sensor frame)
1059  float fgPl[3]; // a posteriori estimate of the gravity vector (sensor frame)
1060  float fmPl[3]; // a posteriori estimate of the geomagnetic vector (sensor frame)
1061  float ftmpA3x3[3][3]; // scratch 3x3 matrix
1062  float ftmpA3x1[3]; // scratch 3x1 vector
1063  float fQvGQa; // accelerometer noise covariance to 1g sphere
1064  float fQvBQd; // magnetometer noise covariance to geomagnetic sphere
1065  float fC6x9ik; // element i, k of measurement matrix C
1066  float fC6x9jk; // element j, k of measurement matrix C
1067  Quaternion fqMi; // a priori orientation quaternion
1068  Quaternion fq6DOF; // eCompass (6DOF accelerometer+magnetometer) orientation quaternion
1069  Quaternion ftmpq; // scratch quaternion used for gyro integration
1070  float fDelta6DOF; // geomagnetic inclination angle computed from accelerometer and magnetometer (deg)
1071  float fsinDelta6DOF; // sin(fDelta6DOF)
1072  float fcosDelta6DOF; // cos(fDelta6DOF)
1073  float fmodGc; // modulus of calibrated accelerometer measurement (g)
1074  float fmodBc; // modulus of calibrated magnetometer measurement (uT)
1075  float ftmp; // scratch float
1076  int8 ierror; // matrix inversion error flag
1077  int8 i,
1078  j,
1079  k; // loop counters
1080 
1081  // working arrays for 6x6 matrix inversion
1082  float *pfRows[6];
1083  int8 iColInd[6];
1084  int8 iRowInd[6];
1085  int8 iPivot[6];
1086 
1087  // if requested, do a reset initialization with no further processing
1088  if (pthisSV->resetflag) {
1089  fInit_9DOF_GBY_KALMAN(pthisSV, pthisAccel, pthisMag, pthisGyro, pthisMagCal);
1090  return;
1091  }
1092 
1093  // compute the average angular velocity (used for display only) from the average measurement minus gyro offset
1094  for (i = CHX; i <= CHZ; i++) pthisSV->fOmega[i] = (float)pthisGyro->iYs[i] * pthisGyro->fDegPerSecPerCount - pthisSV->fbPl[i];
1095 
1096  // initialize the a priori orientation quaternion fqMi to the previous iteration's a posteriori estimate fqPl
1097  // and incrementally rotate fqMi by the contents of the gyro FIFO buffer
1098  fqMi = pthisSV->fqPl;
1099  if (pthisGyro->iFIFOCount > 0) {
1100  // set ftmp to the average interval between FIFO gyro measurements
1101  ftmp = pthisSV->fdeltat / (float)pthisGyro->iFIFOCount;
1102 
1103  // normal case, loop over all the buffered gyroscope measurements
1104  for (j = 0; j < pthisGyro->iFIFOCount; j++) {
1105  // calculate the instantaneous angular velocity subtracting the gyro offset
1106  for (i = CHX; i <= CHZ; i++) ftmpA3x1[i] = (float)pthisGyro->iYsFIFO[j][i] * pthisGyro->fDegPerSecPerCount - pthisSV->fbPl[i];
1107  // compute the incremental rotation quaternion ftmpq and integrate the a priori orientation quaternion fqMi
1108  fQuaternionFromRotationVectorDeg(&ftmpq, ftmpA3x1, ftmp);
1109  qAeqAxB(&fqMi, &ftmpq);
1110  }
1111  } else {
1112  // special case with no new FIFO measurements, use the previous iteration's average gyro reading to compute
1113  // the incremental rotation quaternion ftmpq and integrate the a priori orientation quaternion fqMi
1114  fQuaternionFromRotationVectorDeg(&ftmpq, pthisSV->fOmega, pthisSV->fdeltat);
1115  qAeqAxB(&fqMi, &ftmpq);
1116  }
1117 
1118  // compute the a priori orientation matrix fRMi from the new a priori orientation quaternion fqMi
1119  fRotationMatrixFromQuaternion(fRMi, &fqMi);
1120 
1121  // compute the 6DOF orientation matrix fR6DOF, inclination angle fDelta6DOF and the squared
1122  // deviations of the accelerometer and magnetometer measurements from the 1g gravity and geomagnetic spheres.
1123 #if THISCOORDSYSTEM == NED
1124  feCompassNED(fR6DOF, &fDelta6DOF, &fsinDelta6DOF, &fcosDelta6DOF, pthisMag->fBc, pthisAccel->fGc, &fmodBc, &fmodGc);
1125 #elif THISCOORDSYSTEM == ANDROID
1126  feCompassAndroid(fR6DOF, &fDelta6DOF, &fsinDelta6DOF, &fcosDelta6DOF, pthisMag->fBc, pthisAccel->fGc, &fmodBc, &fmodGc);
1127 #else // WIN8
1128  feCompassWin8(fR6DOF, &fDelta6DOF, &fsinDelta6DOF, &fcosDelta6DOF, pthisMag->fBc, pthisAccel->fGc, &fmodBc, &fmodGc);
1129 #endif
1130 
1131  // compute the 6DOF orientation quaternion fq6DOF from the 6DOF orientation matrix fR6OF
1132  fQuaternionFromRotationMatrix(fR6DOF, &fq6DOF);
1133 
1134  // calculate the acceleration noise variance relative to 1g sphere
1135  ftmp = fmodGc - 1.0F;
1136  fQvGQa = 3.0F * ftmp * ftmp;
1137  if (fQvGQa < FQVG_9DOF_GBY_KALMAN)
1138  fQvGQa = FQVG_9DOF_GBY_KALMAN;
1139 
1140  // calculate magnetic noise variance relative to geomagnetic sphere
1141  ftmp = fmodBc - pthisMagCal->fB;
1142  fQvBQd = 3.0F * ftmp * ftmp;
1143  if (fQvBQd < FQVB_9DOF_GBY_KALMAN)
1144  fQvBQd = FQVB_9DOF_GBY_KALMAN;
1145 
1146  // do a once-only orientation lock immediately after the first valid magnetic calibration by:
1147  // i) setting the a priori and a posteriori orientations to the 6DOF eCompass orientation
1148  // ii) setting the geomagnetic inclination angle fDeltaPl now that the first calibrated 6DOF estimate is available
1149  if (pthisMagCal->iValidMagCal && !pthisSV->iFirstAccelMagLock) {
1150  fqMi = pthisSV->fqPl = fq6DOF;
1151  f3x3matrixAeqB(fRMi, fR6DOF);
1152  pthisSV->fDeltaPl = fDelta6DOF;
1153  pthisSV->fsinDeltaPl = fsinDelta6DOF;
1154  pthisSV->fcosDeltaPl = fcosDelta6DOF;
1155  pthisSV->iFirstAccelMagLock = true;
1156  }
1157 
1158  // set ftmpA3x1 to the normalized 6DOF gravity vector and set fgMi to the normalized a priori gravity vector
1159  // with both estimates computed in the sensor frame
1160 #if THISCOORDSYSTEM == NED
1161  ftmpA3x1[CHX] = fR6DOF[CHX][CHZ];
1162  ftmpA3x1[CHY] = fR6DOF[CHY][CHZ];
1163  ftmpA3x1[CHZ] = fR6DOF[CHZ][CHZ];
1164  fgMi[CHX] = fRMi[CHX][CHZ];
1165  fgMi[CHY] = fRMi[CHY][CHZ];
1166  fgMi[CHZ] = fRMi[CHZ][CHZ];
1167 #else // ANDROID and WIN8 (ENU gravity positive)
1168  ftmpA3x1[CHX] = -fR6DOF[CHX][CHZ];
1169  ftmpA3x1[CHY] = -fR6DOF[CHY][CHZ];
1170  ftmpA3x1[CHZ] = -fR6DOF[CHZ][CHZ];
1171  fgMi[CHX] = -fRMi[CHX][CHZ];
1172  fgMi[CHY] = -fRMi[CHY][CHZ];
1173  fgMi[CHZ] = -fRMi[CHZ][CHZ];
1174 #endif
1175 
1176  // set ftmpq to the quaternion that rotates the 6DOF gravity tilt vector ftmpA3x1 to the a priori estimate fgMi
1177  // and copy its vector components into the measurement error vector fZErr[0-2].
1178  fveqconjgquq(&ftmpq, ftmpA3x1, fgMi);
1179  pthisSV->fZErr[0] = ftmpq.q1;
1180  pthisSV->fZErr[1] = ftmpq.q2;
1181  pthisSV->fZErr[2] = ftmpq.q3;
1182 
1183  // set ftmpA3x1 to the normalized 6DOF geomagnetic vector and set fmMi to the normalized a priori geomagnetic vector
1184  // with both estimates computed in the sensor frame
1185 #if THISCOORDSYSTEM == NED
1186  ftmpA3x1[CHX] = fR6DOF[CHX][CHX] * fcosDelta6DOF + fR6DOF[CHX][CHZ] * fsinDelta6DOF;
1187  ftmpA3x1[CHY] = fR6DOF[CHY][CHX] * fcosDelta6DOF + fR6DOF[CHY][CHZ] * fsinDelta6DOF;
1188  ftmpA3x1[CHZ] = fR6DOF[CHZ][CHX] * fcosDelta6DOF + fR6DOF[CHZ][CHZ] * fsinDelta6DOF;
1189  fmMi[CHX] = fRMi[CHX][CHX] * pthisSV->fcosDeltaPl + fRMi[CHX][CHZ] * pthisSV->fsinDeltaPl;
1190  fmMi[CHY] = fRMi[CHY][CHX] * pthisSV->fcosDeltaPl + fRMi[CHY][CHZ] * pthisSV->fsinDeltaPl;
1191  fmMi[CHZ] = fRMi[CHZ][CHX] * pthisSV->fcosDeltaPl + fRMi[CHZ][CHZ] * pthisSV->fsinDeltaPl;
1192 #else // ANDROID and WIN8 (both ENU coordinate systems)
1193  ftmpA3x1[CHX] = fR6DOF[CHX][CHY] * fcosDelta6DOF - fR6DOF[CHX][CHZ] * fsinDelta6DOF;
1194  ftmpA3x1[CHY] = fR6DOF[CHY][CHY] * fcosDelta6DOF - fR6DOF[CHY][CHZ] * fsinDelta6DOF;
1195  ftmpA3x1[CHZ] = fR6DOF[CHZ][CHY] * fcosDelta6DOF - fR6DOF[CHZ][CHZ] * fsinDelta6DOF;
1196  fmMi[CHX] = fRMi[CHX][CHY] * pthisSV->fcosDeltaPl - fRMi[CHX][CHZ] * pthisSV->fsinDeltaPl;
1197  fmMi[CHY] = fRMi[CHY][CHY] * pthisSV->fcosDeltaPl - fRMi[CHY][CHZ] * pthisSV->fsinDeltaPl;
1198  fmMi[CHZ] = fRMi[CHZ][CHY] * pthisSV->fcosDeltaPl - fRMi[CHZ][CHZ] * pthisSV->fsinDeltaPl;
1199 #endif
1200 
1201  // set ftmpq to the quaternion that rotates the 6DOF geomagnetic tilt vector ftmpA3x1 to the a priori estimate fmMi
1202  // and copy its vector components into the measurement error vector fZErr[3-5].
1203  fveqconjgquq(&ftmpq, ftmpA3x1, fmMi);
1204  pthisSV->fZErr[3] = ftmpq.q1;
1205  pthisSV->fZErr[4] = ftmpq.q2;
1206  pthisSV->fZErr[5] = ftmpq.q3;
1207 
1208  // update Qw using the a posteriori error vectors from the previous iteration.
1209  // as Qv increases or Qw decreases, K -> 0 and the Kalman filter is weighted towards the a priori prediction
1210  // as Qv decreases or Qw increases, KC -> I and the Kalman filter is weighted towards the measurement.
1211  // initialize on and above diagonal elements of Qw to zero
1212  for (i = 0; i < 9; i++)
1213  for (j = i; j < 9; j++)
1214  pthisSV->fQw9x9[i][j] = 0.0F;
1215  // partial diagonal gyro offset terms
1216  pthisSV->fQw9x9[6][6] = pthisSV->fbErrPl[CHX] * pthisSV->fbErrPl[CHX];
1217  pthisSV->fQw9x9[7][7] = pthisSV->fbErrPl[CHY] * pthisSV->fbErrPl[CHY];
1218  pthisSV->fQw9x9[8][8] = pthisSV->fbErrPl[CHZ] * pthisSV->fbErrPl[CHZ];
1219  // set ftmpA3x1 to alpha^2 / 4 * fbErrPl.fbErrPl + fAlphaSqQvYQwbOver12
1220  ftmpA3x1[0] = pthisSV->fAlphaSqOver4 * pthisSV->fQw9x9[6][6] + pthisSV->fAlphaSqQvYQwbOver12;
1221  ftmpA3x1[1] = pthisSV->fAlphaSqOver4 * pthisSV->fQw9x9[7][7] + pthisSV->fAlphaSqQvYQwbOver12;
1222  ftmpA3x1[2] = pthisSV->fAlphaSqOver4 * pthisSV->fQw9x9[8][8] + pthisSV->fAlphaSqQvYQwbOver12;
1223  // diagonal gravity vector components
1224  pthisSV->fQw9x9[0][0] = pthisSV->fqgErrPl[CHX] * pthisSV->fqgErrPl[CHX] + ftmpA3x1[0];
1225  pthisSV->fQw9x9[1][1] = pthisSV->fqgErrPl[CHY] * pthisSV->fqgErrPl[CHY] + ftmpA3x1[1];
1226  pthisSV->fQw9x9[2][2] = pthisSV->fqgErrPl[CHZ] * pthisSV->fqgErrPl[CHZ] + ftmpA3x1[2];
1227  // diagonal geomagnetic vector components
1228  pthisSV->fQw9x9[3][3] = pthisSV->fqmErrPl[CHX] * pthisSV->fqmErrPl[CHX] + ftmpA3x1[0];
1229  pthisSV->fQw9x9[4][4] = pthisSV->fqmErrPl[CHY] * pthisSV->fqmErrPl[CHY] + ftmpA3x1[1];
1230  pthisSV->fQw9x9[5][5] = pthisSV->fqmErrPl[CHZ] * pthisSV->fqmErrPl[CHZ] + ftmpA3x1[2];
1231  // diagonal gyro offset components
1232  pthisSV->fQw9x9[6][6] += pthisSV->fQwbOver3;
1233  pthisSV->fQw9x9[7][7] += pthisSV->fQwbOver3;;
1234  pthisSV->fQw9x9[8][8] += pthisSV->fQwbOver3;;
1235  // set ftmpA3x1 to alpha / 2 * fQw9x9[6-8][6-8]
1236  ftmpA3x1[0] = pthisSV->fAlphaOver2 * pthisSV->fQw9x9[6][6];
1237  ftmpA3x1[1] = pthisSV->fAlphaOver2 * pthisSV->fQw9x9[7][7];
1238  ftmpA3x1[2] = pthisSV->fAlphaOver2 * pthisSV->fQw9x9[8][8];
1239  // off diagonal gravity and gyro offset components
1240  pthisSV->fQw9x9[0][6] = pthisSV->fqgErrPl[CHX] * pthisSV->fbErrPl[CHX] - ftmpA3x1[0];
1241  pthisSV->fQw9x9[1][7] = pthisSV->fqgErrPl[CHY] * pthisSV->fbErrPl[CHY] - ftmpA3x1[1];
1242  pthisSV->fQw9x9[2][8] = pthisSV->fqgErrPl[CHZ] * pthisSV->fbErrPl[CHZ] - ftmpA3x1[2];
1243  // off diagonal geomagnetic and gyro offset components
1244  pthisSV->fQw9x9[3][6] = pthisSV->fqmErrPl[CHX] * pthisSV->fbErrPl[CHX] - ftmpA3x1[0];
1245  pthisSV->fQw9x9[4][7] = pthisSV->fqmErrPl[CHY] * pthisSV->fbErrPl[CHY] - ftmpA3x1[1];
1246  pthisSV->fQw9x9[5][8] = pthisSV->fqmErrPl[CHZ] * pthisSV->fbErrPl[CHZ] - ftmpA3x1[2];
1247  // set below diagonal elements of Qw to above diagonal elements
1248  for (i = 1; i < 9; i++)
1249  for (j = 0; j < i; j++)
1250  pthisSV->fQw9x9[i][j] = pthisSV->fQw9x9[j][i];
1251 
1252  // calculate the vector fQv6x1 containing the diagonal elements of the measurement covariance matrix Qv
1253  pthisSV->fQv6x1[0] = pthisSV->fQv6x1[1] = pthisSV->fQv6x1[2] = ONEOVER12 * fQvGQa + pthisSV->fAlphaSqQvYQwbOver12;
1254  pthisSV->fQv6x1[3] = pthisSV->fQv6x1[4] = pthisSV->fQv6x1[5] = ONEOVER12 * fQvBQd / pthisMagCal->fBSq + pthisSV->fAlphaSqQvYQwbOver12;
1255 
1256  // calculate the Kalman gain matrix K = Qw * C^T * inv(C * Qw * C^T + Qv)
1257  // set fQwCT9x6 = Qw.C^T where Qw has size 9x9 and C^T has size 9x6
1258  for (i = 0; i < 9; i++) { // loop over rows
1259  for (j = 0; j < 6; j++) { // loop over columns
1260  pthisSV->fQwCT9x6[i][j] = 0.0F;
1261  // accumulate matrix sum
1262  for (k = 0; k < 9; k++) {
1263  // determine fC6x9[j][k] since the matrix is highly sparse
1264  fC6x9jk = 0.0F;
1265  // handle rows 0 to 2
1266  if (j < 3) {
1267  if (k == j) fC6x9jk = 1.0F;
1268  if (k == (j + 6)) fC6x9jk = -pthisSV->fAlphaOver2;
1269  } else if (j < 6) {
1270  // handle rows 3 to 5
1271  if (k == j) fC6x9jk = 1.0F;
1272  if (k == (j + 3)) fC6x9jk = -pthisSV->fAlphaOver2;
1273  }
1274 
1275  // accumulate fQwCT9x6[i][j] += Qw9x9[i][k] * C[j][k]
1276  if ((pthisSV->fQw9x9[i][k] != 0.0F) && (fC6x9jk != 0.0F)) {
1277  if (fC6x9jk == 1.0F) pthisSV->fQwCT9x6[i][j] += pthisSV->fQw9x9[i][k];
1278  else pthisSV->fQwCT9x6[i][j] += pthisSV->fQw9x9[i][k] * fC6x9jk;
1279  }
1280  }
1281  }
1282  }
1283 
1284  // set symmetric ftmpA6x6 = C.(Qw.C^T) + Qv = C.fQwCT9x6 + Qv
1285  for (i = 0; i < 6; i++) { // loop over rows
1286  for (j = i; j < 6; j++) { // loop over on and above diagonal columns
1287  // zero off diagonal and set diagonal to Qv
1288  if (i == j) ftmpA6x6[i][j] = pthisSV->fQv6x1[i];
1289  else ftmpA6x6[i][j] = 0.0F;
1290  // accumulate matrix sum
1291  for (k = 0; k < 9; k++) {
1292  // determine fC6x9[i][k]
1293  fC6x9ik = 0.0F;
1294  // handle rows 0 to 2
1295  if (i < 3) {
1296  if (k == i) fC6x9ik = 1.0F;
1297  if (k == (i + 6)) fC6x9ik = -pthisSV->fAlphaOver2;
1298  } else if (i < 6) {
1299  // handle rows 3 to 5
1300  if (k == i) fC6x9ik = 1.0F;
1301  if (k == (i + 3)) fC6x9ik = -pthisSV->fAlphaOver2;
1302  }
1303 
1304  // accumulate ftmpA6x6[i][j] += C[i][k] & fQwCT9x6[k][j]
1305  if ((fC6x9ik != 0.0F) && (pthisSV->fQwCT9x6[k][j] != 0.0F)) {
1306  if (fC6x9ik == 1.0F) ftmpA6x6[i][j] += pthisSV->fQwCT9x6[k][j];
1307  else ftmpA6x6[i][j] += fC6x9ik * pthisSV->fQwCT9x6[k][j];
1308  }
1309  }
1310  }
1311  }
1312  // set ftmpA6x6 below diagonal elements to above diagonal elements
1313  for (i = 1; i < 6; i++) // loop over rows
1314  for (j = 0; j < i; j++) // loop over below diagonal columns
1315  ftmpA6x6[i][j] = ftmpA6x6[j][i];
1316 
1317  // invert ftmpA6x6 in situ to give ftmpA6x6 = inv(C * Qw * C^T + Qv) = inv(ftmpA6x6)
1318  for (i = 0; i < 6; i++)
1319  pfRows[i] = ftmpA6x6[i];
1320  fmatrixAeqInvA(pfRows, iColInd, iRowInd, iPivot, 6, &ierror);
1321 
1322  // on successful inversion set Kalman gain matrix K9x6 = Qw * C^T * inv(C * Qw * C^T + Qv) = fQwCT9x6 * ftmpA6x6
1323  if (!ierror) {
1324  // normal case
1325  for (i = 0; i < 9; i++) // loop over rows
1326  for (j = 0; j < 6; j++) { // loop over columns
1327  pthisSV->fK9x6[i][j] = 0.0F;
1328  for (k = 0; k < 6; k++) {
1329  if ((pthisSV->fQwCT9x6[i][k] != 0.0F) && (ftmpA6x6[k][j] != 0.0F))
1330  pthisSV->fK9x6[i][j] += pthisSV->fQwCT9x6[i][k] * ftmpA6x6[k][j];
1331  }
1332  }
1333  } else {
1334  // ftmpA6x6 was singular so set Kalman gain matrix to zero
1335  for (i = 0; i < 9; i++) // loop over rows
1336  for (j = 0; j < 6; j++) // loop over columns
1337  pthisSV->fK9x6[i][j] = 0.0F;
1338  }
1339 
1340  // calculate the a posteriori gravity and geomagnetic tilt quaternion errors and gyro offset error vector
1341  // from the Kalman matrix fK9x6 and the measurement error vector fZErr.
1342  for (i = CHX; i <= CHZ; i++) {
1343  pthisSV->fqgErrPl[i] = pthisSV->fqmErrPl[i] = pthisSV->fbErrPl[i] = 0.0F;
1344  for (j = 0; j < 6; j++) {
1345  // calculate gravity tilt quaternion vector error component fqgErrPl
1346  if (pthisSV->fK9x6[i][j] != 0.0F)
1347  pthisSV->fqgErrPl[i] += pthisSV->fK9x6[i][j] * pthisSV->fZErr[j];
1348 
1349  // calculate geomagnetic tilt quaternion vector error component fqmErrPl
1350  if (pthisSV->fK9x6[i + 3][j] != 0.0F)
1351  pthisSV->fqmErrPl[i] += pthisSV->fK9x6[i + 3][j] * pthisSV->fZErr[j];
1352 
1353  // calculate gyro offset vector error component fbErrPl
1354  if (pthisSV->fK9x6[i + 6][j] != 0.0F)
1355  pthisSV->fbErrPl[i] += pthisSV->fK9x6[i + 6][j] * pthisSV->fZErr[j];
1356  }
1357  }
1358 
1359  // set ftmpq to the a posteriori gravity tilt correction (conjugate) quaternion
1360  ftmpq.q1 = -pthisSV->fqgErrPl[CHX];
1361  ftmpq.q2 = -pthisSV->fqgErrPl[CHY];
1362  ftmpq.q3 = -pthisSV->fqgErrPl[CHZ];
1363  ftmpq.q0 = sqrtf(fabs(1.0F - ftmpq.q1 * ftmpq.q1 - ftmpq.q2 * ftmpq.q2 - ftmpq.q3 * ftmpq.q3));
1364 
1365  // set ftmpA3x3 to the gravity tilt correction matrix and rotate the normalized a priori estimate of the
1366  // gravity vector fgMi to obtain the normalized a posteriori estimate of the gravity vector fgPl
1367  fRotationMatrixFromQuaternion(ftmpA3x3, &ftmpq);
1368  fveqRu(fgPl, ftmpA3x3, fgMi, 0);
1369 
1370  // set ftmpq to the a posteriori geomagnetic tilt correction (conjugate) quaternion
1371  ftmpq.q1 = -pthisSV->fqmErrPl[CHX];
1372  ftmpq.q2 = -pthisSV->fqmErrPl[CHY];
1373  ftmpq.q3 = -pthisSV->fqmErrPl[CHZ];
1374  ftmpq.q0 = sqrtf(fabs(1.0F - ftmpq.q1 * ftmpq.q1 - ftmpq.q2 * ftmpq.q2 - ftmpq.q3 * ftmpq.q3));
1375 
1376  // set ftmpA3x3 to the geomagnetic tilt correction matrix and rotate the normalized a priori estimate of the
1377  // geomagnetic vector fmMi to obtain the normalized a posteriori estimate of the geomagnetic vector fmPl
1378  fRotationMatrixFromQuaternion(ftmpA3x3, &ftmpq);
1379  fveqRu(fmPl, ftmpA3x3, fmMi, 0);
1380 
1381  // compute the a posteriori orientation matrix fRPl from the vector product of the a posteriori gravity fgPl
1382  // and geomagnetic fmPl vectors both of which are normalized
1383 #if THISCOORDSYSTEM == NED// gravity vector is +z and accel measurement is +z when flat so don't negate
1384  feCompassNED(pthisSV->fRPl, &(pthisSV->fDeltaPl), &(pthisSV->fsinDeltaPl), &(pthisSV->fcosDeltaPl),
1385  fmPl, fgPl, &fmodBc, &fmodGc);
1386 #elif THISCOORDSYSTEM == ANDROID // gravity vector is -z and accel measurement is +z when flat so negate
1387  ftmpA3x1[CHX] = -fgPl[CHX];
1388  ftmpA3x1[CHY] = -fgPl[CHY];
1389  ftmpA3x1[CHZ] = -fgPl[CHZ];
1390  feCompassAndroid(pthisSV->fRPl, &(pthisSV->fDeltaPl), &(pthisSV->fsinDeltaPl), &(pthisSV->fcosDeltaPl),
1391  fmPl, ftmpA3x1, &fmodBc, &fmodGc);
1392 #else // WIN8// gravity vector is -z and accel measurement is -1g when flat so don't negate
1393  feCompassWin8(pthisSV->fRPl, &(pthisSV->fDeltaPl), &(pthisSV->fsinDeltaPl), &(pthisSV->fcosDeltaPl),
1394  fmPl, fgPl, &fmodBc, &fmodGc);
1395 #endif
1396 
1397  // compute the a posteriori quaternion fqPl and rotation vector fRVecPl from fRPl
1398  fQuaternionFromRotationMatrix(pthisSV->fRPl, &(pthisSV->fqPl));
1399  fRotationVectorDegFromQuaternion(&(pthisSV->fqPl), pthisSV->fRVecPl);
1400 
1401  // update the a posteriori gyro offset vector: b+[k] = b-[k] - be+[k] = b+[k] - be+[k] (deg/s)
1402  for (i = CHX; i <= CHZ; i++) {
1403  // restrict the gyro offset correction to the maximum permitted by the random walk model
1404  if (pthisSV->fbErrPl[i] > pthisSV->fMaxGyroOffsetChange)
1405  pthisSV->fbPl[i] -= pthisSV->fMaxGyroOffsetChange;
1406  else if (pthisSV->fbErrPl[i] < -pthisSV->fMaxGyroOffsetChange)
1407  pthisSV->fbPl[i] += pthisSV->fMaxGyroOffsetChange;
1408  else
1409  pthisSV->fbPl[i] -= pthisSV->fbErrPl[i];
1410  // restrict gyro offset between specified limits
1411  if (pthisSV->fbPl[i] > FMAX_9DOF_GBY_BPL) pthisSV->fbPl[i] = FMAX_9DOF_GBY_BPL;
1412  if (pthisSV->fbPl[i] < FMIN_9DOF_GBY_BPL) pthisSV->fbPl[i] = FMIN_9DOF_GBY_BPL;
1413  }
1414 
1415  // compute the linear acceleration fAccGl in the global frame
1416  // first de-rotate the accelerometer measurement fGc from the sensor to global frame
1417  // using the transpose (inverse) of the orientation matrix fRPl
1418  fveqRu(pthisSV->fAccGl, pthisSV->fRPl, pthisAccel->fGc, 1);
1419 
1420  // subtract the fixed gravity vector in the global frame leaving linear acceleration
1421 #if THISCOORDSYSTEM == NED
1422  // gravity positive NED
1423  pthisSV->fAccGl[CHX] = -pthisSV->fAccGl[CHX];
1424  pthisSV->fAccGl[CHY] = -pthisSV->fAccGl[CHY];
1425  pthisSV->fAccGl[CHZ] = -(pthisSV->fAccGl[CHZ] - 1.0F);
1426 #elif THISCOORDSYSTEM == ANDROID
1427  // acceleration positive ENU
1428  pthisSV->fAccGl[CHZ] = pthisSV->fAccGl[CHZ] - 1.0F;
1429 #else // WIN8
1430  // gravity positive ENU
1431  pthisSV->fAccGl[CHX] = -pthisSV->fAccGl[CHX];
1432  pthisSV->fAccGl[CHY] = -pthisSV->fAccGl[CHY];
1433  pthisSV->fAccGl[CHZ] = -(pthisSV->fAccGl[CHZ] + 1.0F);
1434 #endif
1435 
1436  // integrate the acceleration to velocity and displacement in the global frame.
1437  // Note: integration errors accumulate without limit over time and this code should only be
1438  // used for inertial integration of the order of seconds.
1439  for (i = CHX; i <= CHZ; i++) {
1440  // integrate acceleration (in g) to velocity in m/s
1441  pthisSV->fVelGl[i] += pthisSV->fAccGl[i] * pthisSV->fgdeltat;
1442  // integrate velocity (in m/s) to displacement (m)
1443  pthisSV->fDisGl[i] += pthisSV->fVelGl[i] * pthisSV->fdeltat;
1444  }
1445 
1446  // compute the a posteriori Euler angles from the a posteriori orientation matrix fRPl
1447 #if THISCOORDSYSTEM == NED
1448  fNEDAnglesDegFromRotationMatrix(pthisSV->fRPl, &(pthisSV->fPhiPl), &(pthisSV->fThePl), &(pthisSV->fPsiPl), &(pthisSV->fRhoPl), &(pthisSV->fChiPl));
1449 #elif THISCOORDSYSTEM == ANDROID
1450  fAndroidAnglesDegFromRotationMatrix(pthisSV->fRPl, &(pthisSV->fPhiPl), &(pthisSV->fThePl), &(pthisSV->fPsiPl), &(pthisSV->fRhoPl), &(pthisSV->fChiPl));
1451 #else // WIN8
1452  fWin8AnglesDegFromRotationMatrix(pthisSV->fRPl, &(pthisSV->fPhiPl), &(pthisSV->fThePl), &(pthisSV->fPsiPl), &(pthisSV->fRhoPl), &(pthisSV->fChiPl));
1453 #endif
1454 
1455  return;
1456 } // end fRun_9DOF_GBY_KALMAN
1457 #endif // #if F_9DOF_GBY_KALMAN
float fbPl[3]
gyro offset (deg/s)
#define FMIN_6DOF_GY_BPL
minimum permissible power on gyro offsets (deg/s)
Definition: fusion.h:65
#define FLPFSECS_6DOF_GB_BASIC
Definition: fusion.h:57
float fChiPl
tilt from vertical (deg)
SV_3DOF_Y_BASIC structure is the 3DOF basic gyroscope state vector structure.
float fK9x6[9][6]
kalman filter gain matrix K
float flpf
low pass filter coefficient
void fRun_1DOF_P_BASIC(struct SV_1DOF_P_BASIC *pthisSV, struct PressureSensor *pthisPressure)
Definition: fusion.c:447
float fLPRVec[3]
rotation vector
void fmatrixAeqInvA(float *A[], int8 iColInd[], int8 iRowInd[], int8 iPivot[], int8 isize, int8 *pierror)
function uses Gauss-Jordan elimination to compute the inverse of matrix A in situ on exit...
Definition: matrix.c:666
float fDeltaPl
a posteriori inclination angle from Kalman filter (deg)
Quaternion fq
unfiltered orientation quaternion
void f3DOFMagnetometerMatrixNED(float fR[][3], float fBc[])
Aerospace NED magnetometer 3DOF flat eCompass function, computing rotation matrix fR...
Definition: orientation.c:215
void fRotationMatrixFromQuaternion(float R[][3], const Quaternion *pq)
compute the rotation matrix from an orientation quaternion
Definition: orientation.c:822
void fInitializeFusion(SensorFusionGlobals *sfg)
Definition: fusion.c:51
#define FMIN_9DOF_GBY_BPL
minimum permissible power on gyro offsets (deg/s)
Definition: fusion.h:77
int8_t resetflag
flag to request re-initialization on next pass
float fLPH
low pass filtered height (m)
#define CHY
Used to access Y-channel entries in various data data structures.
Definition: sensor_fusion.h:77
float fLPPhi
low pass roll (deg)
void f3DOFTiltNED(float fR[][3], float fGc[])
Aerospace NED accelerometer 3DOF tilt function, computing rotation matrix fR.
float fdeltat
fusion time interval (s)
int32_t loopcounter
counter incrementing each iteration of sensor fusion (typically 25Hz)
float fAlphaQwbOver6
(PI / 180 * fdeltat) * Qwb / 6
float fR[3][3]
unfiltered orientation matrix
float fOmega[3]
virtual gyro angular velocity (deg/s)
void fRun_6DOF_GB_BASIC(struct SV_6DOF_GB_BASIC *pthisSV, struct MagSensor *pthisMag, struct AccelSensor *pthisAccel)
Definition: fusion.c:610
#define FLPFSECS_3DOF_G_BASIC
void f3x3matrixAeqB(float A[][3], float B[][3])
function sets 3x3 matrix A to 3x3 matrix B
Definition: matrix.c:66
#define FQWB_9DOF_GBY_KALMAN
gyro offset random walk units (deg/s)^2
Definition: fusion.h:76
void fAndroidAnglesDegFromRotationMatrix(float R[][3], float *pfPhiDeg, float *pfTheDeg, float *pfPsiDeg, float *pfRhoDeg, float *pfChiDeg)
extract the Android angles in degrees from the Android rotation matrix
Definition: orientation.c:564
float fPsiPl
yaw (deg)
float fLPPsi
low pass yaw (deg)
float fThePl
pitch (deg)
Quaternion fq
unfiltered orientation quaternion
float fDisGl[3]
displacement (m) in global frame
float q0
scalar component
Definition: orientation.h:39
float fThe
pitch (deg)
void fRun_9DOF_GBY_KALMAN(struct SV_9DOF_GBY_KALMAN *pthisSV, struct AccelSensor *pthisAccel, struct MagSensor *pthisMag, struct GyroSensor *pthisGyro, struct MagCalibration *pthisMagCal)
Lower level sensor fusion interface.
int32_t systick
systick timer
#define FPIOVER180
degrees to radians conversion = pi / 180
Definition: sensor_fusion.h:99
float fdeltat
fusion time interval (s)
float fAlphaSqQvYQwbOver12
(PI / 180 * fdeltat)^2 * (QvY + Qwb) / 12
#define FMAX_6DOF_GY_BPL
Functions to convert between various orientation representations.
float fLPRVec[3]
rotation vector
int32_t systick
systick timer
float fQw9x9[9][9]
covariance matrix Qw
float fChi
tilt from vertical (deg)
#define FQVY_6DOF_GY_KALMAN
gyro sensor noise variance units (deg/s)^2
Definition: fusion.h:62
uint8_t iFIFOCount
number of measurements read from FIFO
Quaternion fqPl
a posteriori orientation quaternion
void fRun_3DOF_B_BASIC(struct SV_3DOF_B_BASIC *pthisSV, struct MagSensor *pthisMag)
Definition: fusion.c:517
Quaternion fLPq
low pass filtered orientation quaternion
void fNEDAnglesDegFromRotationMatrix(float R[][3], float *pfPhiDeg, float *pfTheDeg, float *pfPsiDeg, float *pfRhoDeg, float *pfChiDeg)
extract the NED angles in degrees from the NED rotation matrix
Definition: orientation.c:508
float fZErr[3]
measurement error vector
void feCompassAndroid(float fR[][3], float *pfDelta, float *pfsinDelta, float *pfcosDelta, float fBc[], float fGc[], float *pfmodBc, float *pfmodGc)
Android: basic 6DOF e-Compass function, computing rotation matrix fR and magnetic inclination angle f...
Definition: orientation.c:359
float fLPR[3][3]
low pass filtered orientation matrix
The top level fusion structure.
float fLPPhi
low pass roll (deg)
float fdeltat
fusion time interval (s)
float fVelGl[3]
velocity (m/s) in global frame
float fLPRho
low pass compass (deg)
float q3
z vector component
Definition: orientation.h:42
float fLPRho
low pass compass (deg)
int8_t resetflag
flag to request re-initialization on next pass
int8_t resetflag
flag to request re-initialization on next pass
float fAlphaSqOver4
(PI / 180 * fdeltat)^2 / 4
float fRVec[3]
rotation vector
float fLPPsi
low pass yaw (deg)
#define FQVB_9DOF_GBY_KALMAN
magnetometer sensor noise variance units uT^2 defining minimum deviation from geomagnetic sphere...
Definition: fusion.h:75
void fQuaternionFromRotationMatrix(float R[][3], Quaternion *pq)
compute the orientation quaternion from a 3x3 rotation matrix
Definition: orientation.c:781
void fqAeq1(Quaternion *pqA)
set a quaternion to the unit quaternion
Definition: orientation.c:1034
This is the 3DOF basic accelerometer state vector structure.
float fbPl[3]
gyro offset (deg/s)
void fLPFOrientationQuaternion(Quaternion *pq, Quaternion *pLPq, float flpf, float fdeltat, float fOmega[])
function low pass filters an orientation quaternion and computes virtual gyro rotation rate ...
Definition: orientation.c:912
void f3DOFMagnetometerMatrixWin8(float fR[][3], float fBc[])
Windows 8 magnetometer 3DOF flat eCompass function, computing rotation matrix fR. ...
Definition: orientation.c:275
The AccelSensor structure stores raw and processed measurements for a 3-axis accelerometer.
float fLPThe
low pass pitch (deg)
void fInit_9DOF_GBY_KALMAN(struct SV_9DOF_GBY_KALMAN *pthisSV, struct AccelSensor *pthisAccel, struct MagSensor *pthisMag, struct GyroSensor *pthisGyro, struct MagCalibration *pthisMagCal)
Definition: fusion.c:378
float fMaxGyroOffsetChange
maximum permissible gyro offset change per iteration (deg/s)
Magnetic Calibration Structure.
Definition: magnetic.h:72
#define GTOMSEC2
#define FQVG_6DOF_GY_KALMAN
accelerometer sensor noise variance units g^2
Definition: fusion.h:63
void fQuaternionFromRotationVectorDeg(Quaternion *pq, const float rvecdeg[], float fscaling)
computes normalized rotation quaternion from a rotation vector (deg)
Definition: orientation.c:713
float fgdeltat
g (m/s2) * fdeltat
uint32_t uint32
Definition: sensor_fusion.h:60
float fBc[3]
averaged calibrated measurement (uT)
float fcosDeltaPl
cos(fDeltaPl)
Quaternion fLPq
low pass filtered orientation quaternion
float fLPR[3][3]
low pass filtered orientation matrix
float fPhiPl
roll (deg)
void feCompassWin8(float fR[][3], float *pfDelta, float *pfsinDelta, float *pfcosDelta, float fBc[], float fGc[], float *pfmodBc, float *pfmodGc)
Win8: basic 6DOF e-Compass function, computing rotation matrix fR and magnetic inclination angle fDel...
Definition: orientation.c:433
float fOmega[3]
average angular velocity (deg/s)
The SV_1DOF_P_BASIC structure contains state information for a pressure sensor/altimeter.
float fThePl
pitch (deg)
This is the 3DOF basic magnetometer state vector structure/.
float fDelta
unfiltered inclination angle (deg)
quaternion structure definition
Definition: orientation.h:37
float fLPR[3][3]
low pass filtered orientation matrix
float fYs[3]
averaged measurement (deg/s)
int8_t resetflag
flag to request re-initialization on next pass
float fPsiPl
yaw (deg)
void fInit_3DOF_B_BASIC(struct SV_3DOF_B_BASIC *pthisSV, struct MagSensor *pthisMag, float flpftimesecs)
Definition: fusion.c:228
float fLPChi
low pass tilt from vertical (deg)
int32_t iValidMagCal
solver used: 0 (no calibration) or 4, 7, 10 element
Definition: magnetic.h:80
float fLPPsi
low pass yaw (deg)
#define FLPFSECS_3DOF_B_BASIC
The PressureSensor structure stores raw and processed measurements for an altimeter.
float fLPThe
low pass pitch (deg)
float fQw6x6[6][6]
covariance matrix Qw
void fRun_3DOF_Y_BASIC(struct SV_3DOF_Y_BASIC *pthisSV, struct GyroSensor *pthisGyro)
Definition: fusion.c:562
void qAeqBxC(Quaternion *pqA, const Quaternion *pqB, const Quaternion *pqC)
function compute the quaternion product qB * qC
Definition: orientation.c:958
float fR[3][3]
unfiltered orientation matrix
float fOmega[3]
angular velocity (deg/s)
float fR[3][3]
unfiltered orientation matrix
float fbErrPl[3]
gyro offset error (deg/s)
float fPhiPl
roll (deg)
void fInit_6DOF_GB_BASIC(struct SV_6DOF_GB_BASIC *pthisSV, struct AccelSensor *pthisAccel, struct MagSensor *pthisMag, float flpftimesecs)
Definition: fusion.c:277
int32 ARM_systick_elapsed_ticks(int32 start_ticks)
float fLPRVec[3]
rotation vector
float fLPT
low pass filtered temperature (C)
void fveqconjgquq(Quaternion *pfq, float fu[], float fv[])
function computes the rotation quaternion that rotates unit vector u onto unit vector v as v=q*...
Definition: orientation.c:1044
Math approximations file.
Quaternion fq
unfiltered orientation quaternion
float fRhoPl
compass (deg)
float fAlphaQwbOver6
(PI / 180 * fdeltat) * Qwb / 6
float fGc[3]
averaged precision calibrated measurement (g)
float fMaxGyroOffsetChange
maximum permissible gyro offset change per iteration (deg/s)
void fInit_1DOF_P_BASIC(struct SV_1DOF_P_BASIC *pthisSV, struct PressureSensor *pthisPressure, float flpftimesecs)
Definition: fusion.c:172
SV_6DOF_GB_BASIC is the 6DOF basic accelerometer and magnetometer state vector structure.
float flpf
low pass filter coefficient
float fAlphaSqOver4
(PI / 180 * fdeltat)^2 / 4
float fsinDeltaPl
sin(fDeltaPl)
float fChiPl
tilt from vertical (deg)
The sensor_fusion.h file implements the top level programming interface.
int16_t iYs[3]
average measurement (counts)
int32_t systick
systick timer
float fRPl[3][3]
a posteriori orientation matrix
#define CHZ
float fR[3][3]
unfiltered orientation matrix
int32_t systick
systick timer;
#define FMAX_9DOF_GBY_BPL
float fLPChi
low pass tilt from vertical (deg)
Matrix manipulation functions.
float fdeltat
sensor fusion interval (s)
float fQv6x1[6]
measurement noise covariance matrix leading diagonal
float fdeltat
sensor fusion interval (s)
Provides function prototypes for driver level interfaces.
float fLPThe
low pass pitch (deg)
Quaternion fq
unfiltered orientation quaternion
#define FQVY_9DOF_GBY_KALMAN
gyro sensor noise variance units (deg/s)^2
Definition: fusion.h:73
float fH
most recent unaveraged height (m)
void f3DOFTiltAndroid(float fR[][3], float fGc[])
Android accelerometer 3DOF tilt function computing, rotation matrix fR.
float fQwbOver3
Qwb / 3.
#define FQWB_6DOF_GY_KALMAN
gyro offset random walk units (deg/s)^2
Definition: fusion.h:64
int8_t resetflag
flag to request re-initialization on next pass
int8_t resetflag
flag to request re-initialization on next pass
void fInit_3DOF_Y_BASIC(struct SV_3DOF_Y_BASIC *pthisSV)
Definition: fusion.c:259
float fqgErrPl[3]
gravity vector tilt orientation quaternion error (dimensionless)
int16_t iYsFIFO[GYRO_FIFO_SIZE][3]
FIFO measurements (counts)
int8_t iFirstAccelMagLock
denotes that 9DOF orientation has locked to 6DOF eCompass
void fRun_3DOF_G_BASIC(struct SV_3DOF_G_BASIC *pthisSV, struct AccelSensor *pthisAccel)
Definition: fusion.c:467
float fQwbOver3
Qwb / 3.
#define CHX
Used to access X-channel entries in various data data structures.
Definition: sensor_fusion.h:76
float fRVecPl[3]
rotation vector
float fOmega[3]
angular velocity (deg/s)
float fAccGl[3]
linear acceleration (g) in global frame
float fRPl[3][3]
a posteriori orientation matrix
void fveqRu(float fv[], float fR[][3], float fu[], int8 itranspose)
function rotates 3x1 vector u onto 3x1 vector using 3x3 rotation matrix fR. the rotation is applied i...
Definition: matrix.c:820
void fInit_6DOF_GY_KALMAN(struct SV_6DOF_GY_KALMAN *pthisSV, struct AccelSensor *pthisAccel, struct GyroSensor *pthisGyro)
Definition: fusion.c:313
void f3x3matrixAeqI(float A[][3])
function sets the 3x3 matrix A to the identity matrix
Definition: matrix.c:45
float fdeltat
fusion filter sampling interval (s)
float fLPChi
low pass tilt from vertical (deg)
void fInit_3DOF_G_BASIC(struct SV_3DOF_G_BASIC *pthisSV, struct AccelSensor *pthisAccel, float flpftimesecs)
Definition: fusion.c:197
void feCompassNED(float fR[][3], float *pfDelta, float *pfsinDelta, float *pfcosDelta, float fBc[], float fGc[], float *pfmodBc, float *pfmodGc)
NED: basic 6DOF e-Compass function, computing rotation matrix fR and magnetic inclination angle fDelt...
Definition: orientation.c:286
float fOmega[3]
angular velocity (deg/s)
float fPsi
yaw (deg)
float fAlphaOver2
PI / 180 * fdeltat / 2.
float fRho
compass (deg)
void fqAeqNormqA(Quaternion *pqA)
function normalizes a rotation quaternion and ensures q0 is non-negative
Definition: orientation.c:999
float fPhi
roll (deg)
Quaternion fqPl
a posteriori orientation quaternion
float fLPDelta
low pass filtered inclination angle (deg)
Quaternion fLPq
low pass filtered orientation quaternion
float fqgErrPl[3]
gravity vector tilt orientation quaternion error (dimensionless)
float fdeltat
fusion time interval (s)
float fT
most recent unaveraged temperature (C)
float fLPRho
low pass compass (deg)
#define FUSION_HZ
(int) actual rate of fusion algorithm execution and sensor FIFO reads
float fAlphaOver2
PI / 180 * fdeltat / 2.
Defines control sub-system.
The MagSensor structure stores raw and processed measurements for a 3-axis magnetic sensor...
void fRotationVectorDegFromQuaternion(Quaternion *pq, float rvecdeg[])
computes rotation vector (deg) from rotation quaternion
Definition: orientation.c:862
float fZErr[6]
measurement error vector
float fRVecPl[3]
rotation vector
float fRhoPl
compass (deg)
float fB
current geomagnetic field magnitude (uT)
Definition: magnetic.h:77
float fQwCT9x6[9][6]
Qw.C^T matrix.
int8_t resetflag
flag to request re-initialization on next pass
float flpf
low pass filter coefficient
void fRun_6DOF_GY_KALMAN(struct SV_6DOF_GY_KALMAN *pthisSV, struct AccelSensor *pthisAccel, struct GyroSensor *pthisGyro)
Definition: fusion.c:663
float fQwCT6x3[6][3]
Qw.C^T matrix.
SensorFusionGlobals sfg
This is the primary sensor fusion data structure.
float fBSq
square of fB (uT^2)
Definition: magnetic.h:78
#define FLPFSECS_1DOF_P_BASIC
void f3DOFTiltWin8(float fR[][3], float fGc[])
Windows 8 accelerometer 3DOF tilt function computing, rotation matrix fR.
float q2
y vector component
Definition: orientation.h:41
float fbErrPl[3]
gyro offset error (deg/s)
int32_t systick
systick timer
void qAeqAxB(Quaternion *pqA, const Quaternion *pqB)
function compute the quaternion product qA = qA * qB
Definition: orientation.c:969
float fK6x3[6][3]
kalman filter gain matrix K
float fQv
measurement noise covariance matrix leading diagonal
float fDegPerSecPerCount
deg/s per count
float fLPPhi
low pass roll (deg)
float fOmega[3]
average angular velocity (deg/s)
The GyroSensor structure stores raw and processed measurements for a 3-axis gyroscope.
float flpf
low pass filter coefficient
int8_t int8
Definition: sensor_fusion.h:55
#define ONEOVER12
1 / 12
float q1
x vector component
Definition: orientation.h:40
void fFuseSensors(struct SV_1DOF_P_BASIC *pthisSV_1DOF_P_BASIC, struct SV_3DOF_G_BASIC *pthisSV_3DOF_G_BASIC, struct SV_3DOF_B_BASIC *pthisSV_3DOF_B_BASIC, struct SV_3DOF_Y_BASIC *pthisSV_3DOF_Y_BASIC, struct SV_6DOF_GB_BASIC *pthisSV_6DOF_GB_BASIC, struct SV_6DOF_GY_KALMAN *pthisSV_6DOF_GY_KALMAN, struct SV_9DOF_GBY_KALMAN *pthisSV_9DOF_GBY_KALMAN, struct AccelSensor *pthisAccel, struct MagSensor *pthisMag, struct GyroSensor *pthisGyro, struct PressureSensor *pthisPressure, struct MagCalibration *pthisMagCal)
Definition: fusion.c:86
SV_6DOF_GY_KALMAN is the 6DOF Kalman filter accelerometer and gyroscope state vector structure...
float fqmErrPl[3]
geomagnetic vector tilt orientation quaternion error (dimensionless)
int32_t systick
systick timer
void ARM_systick_start_ticks(int32 *pstart)
SV_9DOF_GBY_KALMAN is the 9DOF Kalman filter accelerometer, magnetometer and gyroscope state vector s...
void f3DOFMagnetometerMatrixAndroid(float fR[][3], float fBc[])
Android magnetometer 3DOF flat eCompass function, computing rotation matrix fR.
Definition: orientation.c:245
#define FQVG_9DOF_GBY_KALMAN
accelerometer sensor noise variance units g^2 defining minimum deviation from 1g sphere ...
Definition: fusion.h:74
float fAlphaSqQvYQwbOver12
(PI / 180 * fdeltat)^2 * (QvY + Qwb) / 12
int32_t systick
systick timer;
float fAccGl[3]
linear acceleration (g) in global frame
void fWin8AnglesDegFromRotationMatrix(float R[][3], float *pfPhiDeg, float *pfTheDeg, float *pfPsiDeg, float *pfRhoDeg, float *pfChiDeg)
extract the Windows 8 angles in degrees from the Windows 8 rotation matrix
Definition: orientation.c:621